From a8471093b68e4bfd0cceca605a9a7db846346856 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:47:07 +0200 Subject: [PATCH 01/17] feat(mod): manifest, entry points and locale --- .github/workflows/check.yml | 12 ++++++++++++ changelog.txt | 7 +++++++ control.lua | 1 + data-final-fixes.lua | 1 + data.lua | 2 ++ info.json | 9 +++++++++ locale/en/hattorio.cfg | 26 ++++++++++++++++++++++++++ settings.lua | 1 + wiki/The-mathematics.md | 9 ++++++--- 9 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 changelog.txt create mode 100644 control.lua create mode 100644 data-final-fixes.lua create mode 100644 data.lua create mode 100644 info.json create mode 100644 locale/en/hattorio.cfg create mode 100644 settings.lua diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 97195d9..07b8462 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,6 +17,18 @@ jobs: - run: pip install pytest - name: verify the tiling and regenerate data run: python -m pytest tools/tests -q + - name: validate the mod manifest and changelog + run: | + python3 -c " + import json + d = json.load(open('info.json')) + assert d['factorio_version'] == '2.0', d['factorio_version'] + for k in ('name','version','title','author','description','dependencies'): + assert k in d, k + sep = open('changelog.txt').readline().rstrip('\n') + assert len(sep) == 99 and set(sep) == {'-'}, 'changelog separator must be 99 dashes' + print('manifest ok:', d['name'], d['version']) + " core: name: tiling core (Lua 5.2) diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..353378e --- /dev/null +++ b/changelog.txt @@ -0,0 +1,7 @@ +--------------------------------------------------------------------------------------------------- +Version: 0.1.0 +Date: 2026-09-20 + Major Features: + - Planets are divided into cells of the aperiodic hat monotile tiling. + - Cells are separated by narrow unbuildable bands. Undergrounds tunnel + beneath them; rails cross them; bots and power ignore them. diff --git a/control.lua b/control.lua new file mode 100644 index 0000000..d51bb12 --- /dev/null +++ b/control.lua @@ -0,0 +1 @@ +require("mod.terrain") diff --git a/data-final-fixes.lua b/data-final-fixes.lua new file mode 100644 index 0000000..dbad61c --- /dev/null +++ b/data-final-fixes.lua @@ -0,0 +1 @@ +require("prototypes.patch") diff --git a/data.lua b/data.lua new file mode 100644 index 0000000..3a6ae92 --- /dev/null +++ b/data.lua @@ -0,0 +1,2 @@ +require("prototypes.collision") +require("prototypes.tiles") diff --git a/info.json b/info.json new file mode 100644 index 0000000..f377dcb --- /dev/null +++ b/info.json @@ -0,0 +1,9 @@ +{ + "name": "hattorio", + "version": "0.1.0", + "title": "Hattorio", + "author": "vetkat", + "factorio_version": "2.0", + "description": "Replaces the square grid with the aperiodic hat monotile tiling. Cells never repeat, so neither can your blueprints.", + "dependencies": ["base >= 2.0.0", "? space-age"] +} diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg new file mode 100644 index 0000000..d000b07 --- /dev/null +++ b/locale/en/hattorio.cfg @@ -0,0 +1,26 @@ +[mod-setting-name] +hattorio-hat-size-nauvis=Cell size (Nauvis) +hattorio-band-width-nauvis=Band width (Nauvis) +hattorio-hat-size-vulcanus=Cell size (Vulcanus) +hattorio-band-width-vulcanus=Band width (Vulcanus) +hattorio-hat-size-fulgora=Cell size (Fulgora) +hattorio-band-width-fulgora=Band width (Fulgora) +hattorio-hat-size-gleba=Cell size (Gleba) +hattorio-band-width-gleba=Band width (Gleba) +hattorio-hat-size-aquilo=Cell size (Aquilo) +hattorio-band-width-aquilo=Band width (Aquilo) + +[mod-setting-description] +hattorio-hat-size-nauvis=Distance from a cell's centre to its furthest corner, in tiles. Larger cells give more room and let larger blueprints be reused. +hattorio-band-width-nauvis=Width of the unbuildable band between cells, in tiles. Keep below underground belt reach or cells become isolated. +hattorio-hat-size-vulcanus=Distance from a cell's centre to its furthest corner, in tiles. +hattorio-band-width-vulcanus=Width of the unbuildable band between cells, in tiles. +hattorio-hat-size-fulgora=Distance from a cell's centre to its furthest corner, in tiles. +hattorio-band-width-fulgora=Width of the unbuildable band between cells, in tiles. +hattorio-hat-size-gleba=Distance from a cell's centre to its furthest corner, in tiles. +hattorio-band-width-gleba=Width of the unbuildable band between cells, in tiles. +hattorio-hat-size-aquilo=Distance from a cell's centre to its furthest corner, in tiles. +hattorio-band-width-aquilo=Width of the unbuildable band between cells, in tiles. + +[tile-name] +hattorio-band=Cell boundary diff --git a/settings.lua b/settings.lua new file mode 100644 index 0000000..8e1c2f5 --- /dev/null +++ b/settings.lua @@ -0,0 +1 @@ +require("prototypes.settings") diff --git a/wiki/The-mathematics.md b/wiki/The-mathematics.md index 2f00794..c4b73cb 100644 --- a/wiki/The-mathematics.md +++ b/wiki/The-mathematics.md @@ -36,9 +36,12 @@ integers with `φ² = φ+1` and `√3² = 3` closing multiplication. That works beautifully offline. In Lua it collapses. -Lua 5.2 has no integer type; every number is a double, exact only to 2⁵³. The -substitution's per-level rules are exact rationals *converging on irrational -limits*, so their numerators explode: +Lua 5.2 has no integer subtype — its single number type is a double in any +standard build — so integers are exact only up to 2⁵³. (Lua 5.3 added a +genuine 64-bit integer, which is exactly why this project's tests pin 5.2: +running them on 5.4 would mask the all-doubles behaviour the core depends on). +The substitution's per-level rules are exact rationals *converging on +irrational limits*, so their numerators explode: ``` level 6: numerators 1.4e14 fine From 88baa91300788d0b4f1e8f5e0cebd89e271bd720 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:47:35 +0200 Subject: [PATCH 02/17] feat(mod): band collision layer and per-planet band tiles --- .github/workflows/check.yml | 2 +- .luacheckrc | 25 ++++++++++++++++++++++++- Makefile | 2 +- mod/planets.lua | 21 +++++++++++++++++++++ prototypes/collision.lua | 10 ++++++++++ prototypes/tiles.lua | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 mod/planets.lua create mode 100644 prototypes/collision.lua create mode 100644 prototypes/tiles.lua diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 07b8462..ab45aad 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -44,4 +44,4 @@ jobs: - run: luarocks install busted - run: luarocks install luacheck - run: busted - - run: luacheck hat spec + - run: luacheck hat mod prototypes spec diff --git a/.luacheckrc b/.luacheckrc index ec09e8f..3dec228 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -1,3 +1,26 @@ std = "lua52" -files["spec"] = { std = "+busted" } max_line_length = 100 + +-- Factorio's Lua environment. These are provided by the game, not by Lua. +local factorio_globals = { + "data", "mods", "settings", "defines", "game", "script", "storage", + "prototypes", "remote", "commands", "rendering", "log", "serpent", + "table_size", "helpers", +} + +files["prototypes"] = { + read_globals = factorio_globals, + -- Factorio adds table.deepcopy + globals = { "table" }, +} + +files["mod"] = { + read_globals = factorio_globals, + globals = { "table" }, +} + +files["spec"] = { std = "+busted" } + +-- hat/ is deliberately free of Factorio dependencies: it must load under +-- plain lua5.2 so it can be tested in CI. No Factorio globals here. +files["hat"] = {} diff --git a/Makefile b/Makefile index 4a2aa7a..810866a 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ preview: done lint: - luacheck hat spec + luacheck hat mod prototypes spec check: pytest test lint diff --git a/mod/planets.lua b/mod/planets.lua new file mode 100644 index 0000000..5743416 --- /dev/null +++ b/mod/planets.lua @@ -0,0 +1,21 @@ +-- Which surfaces get tiled, and what the band looks like there. +-- +-- base_tile is a vanilla tile whose graphics and transitions are cloned; only +-- the name, colours and collision mask change. Cloning is what makes this +-- affordable: authoring tile transition graphics from scratch is the +-- expensive part, and vanilla has already done it. +-- +-- NOTE: map_color differs per planet but the in-world graphics do not yet, +-- since they come from base_tile. Real per-planet art is a later job. +return { + nauvis = { base_tile = "stone-path", map_color = { 43, 47, 54 }, + mirror_color = { 20, 121, 201 } }, + vulcanus = { base_tile = "stone-path", map_color = { 38, 26, 24 }, + mirror_color = { 176, 64, 32 } }, + fulgora = { base_tile = "stone-path", map_color = { 46, 38, 56 }, + mirror_color = { 150, 96, 200 } }, + gleba = { base_tile = "stone-path", map_color = { 34, 46, 32 }, + mirror_color = { 120, 170, 70 } }, + aquilo = { base_tile = "stone-path", map_color = { 48, 56, 62 }, + mirror_color = { 120, 180, 210 } }, +} diff --git a/prototypes/collision.lua b/prototypes/collision.lua new file mode 100644 index 0000000..5a08e95 --- /dev/null +++ b/prototypes/collision.lua @@ -0,0 +1,10 @@ +-- A layer the band tiles carry and buildings are given in data-final-fixes. +-- +-- Two prototypes collide only if they SHARE a layer, so the character, +-- vehicles and rails -- which never receive this one -- cross bands freely, +-- while anything that does receive it cannot be placed on one. The game then +-- refuses the placement natively: red preview, blueprints skip the tiles, +-- construction bots never attempt them. +data:extend({ + { type = "collision-layer", name = "hattorio_band" }, +}) diff --git a/prototypes/tiles.lua b/prototypes/tiles.lua new file mode 100644 index 0000000..23a53cf --- /dev/null +++ b/prototypes/tiles.lua @@ -0,0 +1,37 @@ +local planets = require("mod.planets") + +-- A TilePrototype requires name, type, collision_mask, layer, variants and +-- map_color. Cloning a vanilla tile inherits variants, transitions and layer, +-- which is the expensive part to author. +-- +-- walking_speed_modifier is cleared so it falls back to its default of 1: a +-- band must not grant the free walking bonus its base tile carries. +local function band_tile(name, base, color) + local src = data.raw.tile[base] + if not src then + error("hattorio: base tile '" .. base .. "' does not exist") + end + local t = table.deepcopy(src) + t.name = name + t.map_color = color + t.walking_speed_modifier = nil + t.minable = nil + t.mined_sound = nil + t.can_be_part_of_blueprint = false + t.autoplace = nil + t.order = "z[hattorio]" + t.localised_name = { "tile-name.hattorio-band" } + -- Only the band layer: the tile itself blocks nothing else, so the + -- character and vehicles pass over it. + t.collision_mask = { layers = { hattorio_band = true } } + return t +end + +local tiles = {} +for planet, cfg in pairs(planets) do + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet, cfg.base_tile, cfg.map_color) + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet .. "-mirror", + cfg.base_tile, cfg.mirror_color) +end + +data:extend(tiles) From dcc5a79f1ad7a79b277e73965e2ca740169e0787 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:51:01 +0200 Subject: [PATCH 03/17] feat(mod): startup settings and pure geometry derivation Refits the richness compensation against measured drillable fractions. The coefficients in the plan overestimated reachable ore by up to 26 percentage points; the fitted form is accurate to 3.8. A calibration test pins it to the measurements so it cannot drift silently. --- mod/config.lua | 72 +++++++++++++++++++++++ prototypes/settings.lua | 36 ++++++++++++ spec/config_spec.lua | 123 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 mod/config.lua create mode 100644 prototypes/settings.lua create mode 100644 spec/config_spec.lua diff --git a/mod/config.lua b/mod/config.lua new file mode 100644 index 0000000..052e8a0 --- /dev/null +++ b/mod/config.lua @@ -0,0 +1,72 @@ +-- Settings numbers in, geometry numbers out. +-- +-- Pure: no Factorio API, so this runs under plain Lua in CI alongside hat/. +-- Everything that reads `settings` or `storage` lives in mod/surface.lua. + +local Ti = require("hat.tiling") + +local Config = {} + +Config.MIN_SIZE, Config.MAX_SIZE = 15, 90 +Config.MIN_BAND, Config.MAX_BAND = 1, 6 +Config.DEFAULT_SIZE, Config.DEFAULT_BAND = 26, 2 + +-- A Factorio map is 2e6 x 2e6, so its half-diagonal is ~1.41e6. Ti:coverage() +-- is the CIRCUMRADIUS of a hexagonal root, and a square region inscribed in +-- that circle is not fully tiled -- its corners fall outside. So ask for +-- comfortably more than the half-diagonal, never the half-width. +Config.REQUIRED_COVER = 1.6e6 + +local function clamp1(v, lo, hi, default) + if type(v) ~= "number" then return default end + if v ~= v then return lo end -- x ~= x is the only NaN test + if v < lo then return lo end + if v > hi then return hi end + return v +end + +function Config.clamp(size, band) + return clamp1(size, Config.MIN_SIZE, Config.MAX_SIZE, Config.DEFAULT_SIZE), + clamp1(band, Config.MIN_BAND, Config.MAX_BAND, Config.DEFAULT_BAND) +end + +--- Everything the mod needs to place cells, derived from two settings. +-- @return { size, band, unit, depth, half } +function Config.geometry(size, band) + size, band = Config.clamp(size, band) + local unit = Ti.unit_for_hat_size(size) + local depth = Ti.depth_covering(unit, Config.REQUIRED_COVER) + if not depth then + error("hattorio: no emitted depth covers the map at cell size " .. size) + end + return { size = size, band = band, unit = unit, depth = depth, half = band / 2 } +end + +--- Fraction of a cell's ore a 3x3 mining drill can actually reach. +-- +-- Bands cost ore twice: the tiles they occupy, and the clearance a drill needs +-- near an edge. The loss turns out to be (1 - drillable) * size ~ constant for +-- a given band width, so the shortfall scales as band/size. +-- +-- Fitted to rasterised measurements, worst error 3.8 percentage points: +-- size/band measured this formula +-- 20/1 0.511 0.515 +-- 20/2 0.398 0.360 +-- 26/2 0.522 0.508 +-- 41/2 0.682 0.688 +-- 41/3 0.610 0.612 +-- 52/2 0.749 0.754 +function Config.drillable_fraction(size, band) + size, band = Config.clamp(size, band) + local d = 1 - (3.1 * band + 6.6) / size + if d < 0.2 then d = 0.2 end + if d > 1.0 then d = 1.0 end + return d +end + +--- How much to raise ore richness to offset what bands make unreachable. +function Config.richness_multiplier(size, band) + return 1 / Config.drillable_fraction(size, band) +end + +return Config diff --git a/prototypes/settings.lua b/prototypes/settings.lua new file mode 100644 index 0000000..6f10cab --- /dev/null +++ b/prototypes/settings.lua @@ -0,0 +1,36 @@ +local planets = require("mod.planets") + +-- STARTUP, not runtime. The geometry decides terrain, so changing it under an +-- existing save would leave every generated chunk built to the old geometry. +-- mod/surface.lua stores the values per surface at creation, so a later change +-- affects only NEW surfaces and nothing ever seams. +-- +-- The bounds are repeated here rather than required from mod/config.lua: the +-- settings stage cannot load a module that pulls in hat/tiling.lua's generated +-- data. A test in spec/config_spec.lua keeps the two in step. +local settings_list = {} +local order = 0 + +for planet, _ in pairs(planets) do + order = order + 1 + settings_list[#settings_list + 1] = { + type = "int-setting", + name = "hattorio-hat-size-" .. planet, + setting_type = "startup", + default_value = 26, + minimum_value = 15, + maximum_value = 90, + order = string.format("a[size]-%02d[%s]", order, planet), + } + settings_list[#settings_list + 1] = { + type = "int-setting", + name = "hattorio-band-width-" .. planet, + setting_type = "startup", + default_value = 2, + minimum_value = 1, + maximum_value = 6, + order = string.format("b[band]-%02d[%s]", order, planet), + } +end + +data:extend(settings_list) diff --git a/spec/config_spec.lua b/spec/config_spec.lua new file mode 100644 index 0000000..eca91f8 --- /dev/null +++ b/spec/config_spec.lua @@ -0,0 +1,123 @@ +local Config = require("mod.config") +local Ti = require("hat.tiling") + +describe("config", function() + it("clamps out-of-range settings", function() + local s, b = Config.clamp(3, 99) + assert.are.equal(Config.MIN_SIZE, s) + assert.are.equal(Config.MAX_BAND, b) + s, b = Config.clamp(1000, -5) + assert.are.equal(Config.MAX_SIZE, s) + assert.are.equal(Config.MIN_BAND, b) + end) + + it("leaves valid settings alone", function() + local s, b = Config.clamp(26, 2) + assert.are.equal(26, s) + assert.are.equal(2, b) + end) + + it("falls back to defaults for nil or NaN", function() + local s, b = Config.clamp(nil, nil) + assert.are.equal(Config.DEFAULT_SIZE, s) + assert.are.equal(Config.DEFAULT_BAND, b) + local nan = 0 / 0 + s, b = Config.clamp(nan, nan) + assert.are.equal(Config.MIN_SIZE, s) + assert.are.equal(Config.MIN_BAND, b) + end) + + it("derives a unit that yields the requested hat size", function() + for _, size in ipairs({ 15, 26, 41, 90 }) do + local g = Config.geometry(size, 2) + assert.is_true(math.abs(Ti.hat_size_for_unit(g.unit) - size) < 1e-9, + "size " .. size) + end + end) + + it("derives a depth that covers the whole map", function() + for _, size in ipairs({ 15, 26, 41, 90 }) do + local g = Config.geometry(size, 2) + local t = Ti.new({ unit = g.unit, depth = g.depth }) + assert.is_true(t:coverage() >= Config.REQUIRED_COVER, "size " .. size) + end + end) + + it("uses a deeper root for a smaller cell", function() + assert.is_true(Config.geometry(15, 2).depth > Config.geometry(90, 2).depth) + end) + + it("carries half the band width for distance tests", function() + assert.are.equal(1, Config.geometry(26, 2).half) + assert.are.equal(1.5, Config.geometry(26, 3).half) + end) + + it("compensates richness more when less ore is drillable", function() + local small = Config.richness_multiplier(15, 2) + local large = Config.richness_multiplier(90, 2) + assert.is_true(small > large, small .. " should exceed " .. large) + assert.is_true(large >= 1.0) + assert.is_true(small <= 5.0, "compensation is capped at 5x") + end) + + it("caps compensation at the smallest cells", function() + -- At size 15 / band 2 barely 15% of a cell's ore is reachable, so the + -- drillable floor of 0.2 binds and compensation saturates at 5x. That is + -- deliberate: an uncapped formula would hand out absurd richness at the + -- extreme end of the settings range. + assert.are.equal(0.2, Config.drillable_fraction(15, 2)) + assert.are.equal(5.0, Config.richness_multiplier(15, 2)) + -- the default is comfortably off the floor + assert.is_true(Config.drillable_fraction(26, 2) > 0.4) + end) + + it("compensates more for a wider band", function() + assert.is_true(Config.richness_multiplier(26, 4) > Config.richness_multiplier(26, 1)) + end) + + it("defaults to 26 and 2", function() + assert.are.equal(26, Config.DEFAULT_SIZE) + assert.are.equal(2, Config.DEFAULT_BAND) + end) + + it("matches the bounds declared in prototypes/settings.lua", function() + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + assert.is_not_nil(src:find("default_value = " .. Config.DEFAULT_SIZE)) + assert.is_not_nil(src:find("minimum_value = " .. Config.MIN_SIZE)) + assert.is_not_nil(src:find("maximum_value = " .. Config.MAX_SIZE)) + assert.is_not_nil(src:find("default_value = " .. Config.DEFAULT_BAND)) + assert.is_not_nil(src:find("maximum_value = " .. Config.MAX_BAND)) + end) +end) + +describe("richness calibration", function() + + + -- Measured by rasterising a cell's interior and testing where a 3x3 drill + -- fits, worst case across all 12 orientations. If the formula drifts from + -- these, ore compensation silently becomes wrong. + local measured = { + { 20, 1, 0.511 }, { 20, 2, 0.398 }, { 26, 2, 0.522 }, + { 41, 2, 0.682 }, { 41, 3, 0.610 }, { 52, 2, 0.749 }, + } + + it("predicts the measured drillable fractions", function() + for _, m in ipairs(measured) do + local size, band, want = m[1], m[2], m[3] + local got = Config.drillable_fraction(size, band) + assert.is_true(math.abs(got - want) < 0.05, + string.format("size %d band %d: predicted %.3f, measured %.3f", + size, band, got, want)) + end + end) + + it("gives a compensation that restores roughly vanilla yield", function() + for _, m in ipairs(measured) do + local size, band, want = m[1], m[2], m[3] + local effective = want * Config.richness_multiplier(size, band) + assert.is_true(effective > 0.85 and effective < 1.25, + string.format("size %d band %d: effective yield %.2f", size, band, effective)) + end + end) +end) From eeacf60216cc5cf1befa600c441f14c7a4fa699a Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:52:25 +0200 Subject: [PATCH 04/17] feat(mod): give buildable structures the band collision layer Uses collision_mask_util.get_mask rather than assigning a fresh mask: a prototype that does not declare collision_mask inherits a per-type default, and overwriting it would strip the entity of every normal collision. --- prototypes/patch.lua | 63 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 prototypes/patch.lua diff --git a/prototypes/patch.lua b/prototypes/patch.lua new file mode 100644 index 0000000..898f18c --- /dev/null +++ b/prototypes/patch.lua @@ -0,0 +1,63 @@ +-- data-final-fixes, so this sees every entity every other mod has added. +-- +-- Two prototypes collide only if they SHARE a collision layer. Giving +-- buildable structures the band layer makes the game refuse to place them on +-- a band natively: red preview, blueprints skip the tiles, construction bots +-- never attempt them, and no ghosts are left behind. Script-side rejection +-- would place the entity and then remove it, which is visibly worse and needs +-- four event handlers plus ghost cleanup to approximate this. +-- +-- CAREFUL: a prototype that does not declare collision_mask inherits a +-- per-type default. Assigning a fresh mask would WIPE that default and strip +-- the entity of its normal collisions. collision_mask_util.get_mask returns +-- the explicit mask or the type default, which is the only safe way to read +-- it in the data stage. + +local cmu = require("__core__/lualib/collision-mask-util") + +local LAYER = "hattorio_band" + +-- Player-built structures. Deliberately ABSENT, each for a reason: +-- * rails, signals, ramps -- the spec exempts rails so trains still work +-- * vehicles -- you must be able to drive across a band +-- * character -- you must be able to walk across a band +-- * tree, cliff, resource, enemy types -- placed by map generation, and +-- must be free to sit on a band; blocking them would fight terrain gen +local BLOCKED_TYPES = { + "accumulator", "agricultural-tower", "ammo-turret", "arithmetic-combinator", + "artillery-turret", "assembling-machine", "asteroid-collector", "beacon", + "boiler", "burner-generator", "cargo-landing-pad", "constant-combinator", + "container", "decider-combinator", "display-panel", + "electric-energy-interface", "electric-pole", "electric-turret", + "fluid-turret", "furnace", "fusion-generator", "fusion-reactor", "gate", + "generator", "heat-interface", "heat-pipe", "heating-tower", + "infinity-container", "infinity-pipe", "inserter", "lab", "lamp", + "land-mine", "lightning-attractor", "linked-belt", "linked-container", + "loader", "loader-1x1", "logistic-container", "mining-drill", + "offshore-pump", "pipe", "pipe-to-ground", "power-switch", + "programmable-speaker", "pump", "radar", "reactor", "roboport", + "rocket-silo", "selector-combinator", "simple-entity-with-owner", + "solar-panel", "splitter", "storage-tank", "thruster", "train-stop", + "transport-belt", "underground-belt", "valve", "wall", +} + +local patched, skipped = 0, 0 + +for _, type_name in ipairs(BLOCKED_TYPES) do + for name, proto in pairs(data.raw[type_name] or {}) do + -- get_mask errors on a type it has no default for, which a mod can + -- introduce. Failing open (buildable on bands) beats failing to load. + local ok, mask = pcall(cmu.get_mask, proto) + if ok and mask then + mask.layers = mask.layers or {} + mask.layers[LAYER] = true + proto.collision_mask = mask + patched = patched + 1 + else + skipped = skipped + 1 + log("hattorio: could not read collision mask of " .. type_name .. "/" .. name) + end + end +end + +log("hattorio: added " .. LAYER .. " to " .. patched .. " entity prototypes, skipped " .. skipped) From 089837798eeeb0dc0adccd1f4f25b3fc6b0a07a0 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:55:46 +0200 Subject: [PATCH 05/17] feat(mod): per-surface geometry and band rasterisation Rasterises each hat edge into its own bounding box rather than testing every tile against every edge: 39.7 ms/chunk -> 1.26 ms, a 31x speedup with identical output. The naive form would have stuttered visibly while exploring. tools/bench.lua now gates both stages. --- .luacheckrc | 10 +++-- mod/bands.lua | 100 ++++++++++++++++++++++++++++++++++++++++++++ mod/surface.lua | 57 +++++++++++++++++++++++++ spec/bands_spec.lua | 72 +++++++++++++++++++++++++++++++ tools/bench.lua | 22 +++++++++- 5 files changed, 257 insertions(+), 4 deletions(-) create mode 100644 mod/bands.lua create mode 100644 mod/surface.lua create mode 100644 spec/bands_spec.lua diff --git a/.luacheckrc b/.luacheckrc index 3dec228..b462679 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -3,20 +3,24 @@ max_line_length = 100 -- Factorio's Lua environment. These are provided by the game, not by Lua. local factorio_globals = { - "data", "mods", "settings", "defines", "game", "script", "storage", + "data", "mods", "settings", "defines", "game", "script", "prototypes", "remote", "commands", "rendering", "log", "serpent", "table_size", "helpers", } +-- storage is the mod's persistent table: Factorio provides it, mods write to +-- it, so it is not read-only. +local factorio_writable = { "storage", "table" } + files["prototypes"] = { read_globals = factorio_globals, -- Factorio adds table.deepcopy - globals = { "table" }, + globals = factorio_writable, } files["mod"] = { read_globals = factorio_globals, - globals = { "table" }, + globals = factorio_writable, } files["spec"] = { std = "+busted" } diff --git a/mod/bands.lua b/mod/bands.lua new file mode 100644 index 0000000..011a31b --- /dev/null +++ b/mod/bands.lua @@ -0,0 +1,100 @@ +-- Which tiles of an area fall on a band. +-- +-- Pure: no Factorio API, so this runs under plain Lua in CI. A tile is a band +-- tile when its centre lies within half the band width of any hat edge. +-- +-- PERFORMANCE. The obvious implementation -- test every tile against every +-- edge -- is O(tiles x hats x edges) and measured 39.7 ms per chunk, fifty +-- times the cost of the tiling descent itself. Instead each edge is +-- rasterised into its own bounding box, clipped to the area, which touches +-- only tiles that could possibly be near it. +-- +-- Mirror marking: a reflected hat paints its own edges in the mirror variant, +-- and where a reflected and an unreflected hat share an edge the reflected one +-- wins. That is an OR over the hats covering a tile, so it does not depend on +-- iteration order -- which matters, because two clients must agree. + +local G = require("hat.geometry") + +local Bands = {} + +local floor, ceil = math.floor, math.ceil + +--- @param tiling a hat.tiling instance +--- @param geom from mod.config.geometry, plus tile names +--- @param area { left, top, right, bottom } in tile coordinates; right and +--- bottom are exclusive, matching Factorio's chunk areas +--- @return array of { x = , y = , mirror = boolean }, in scan order +function Bands.for_area(tiling, geom, area) + local unit, half = geom.unit, geom.half + + -- Query wider than the area: a hat centred outside it can still own an edge + -- running through it. + local pad = geom.band + 2 + local hats = tiling:hats_in_box({ + left = area.left - pad, top = area.top - pad, + right = area.right + pad, bottom = area.bottom + pad, + }) + + local left, top = area.left, area.top + local right, bottom = area.right - 1, area.bottom - 1 + + -- rows[ty][tx] = false for a plain band tile, true for a mirror one + local rows = {} + local dist = G.dist_to_segment + + for i = 1, #hats do + local mirror = hats[i].reflected + local edges = G.edges(hats[i].xf, unit) + for j = 1, #edges do + local s = edges[j] + local x1, y1, x2, y2 = s[1], s[2], s[3], s[4] + + -- only the tiles within `half` of this segment's bounding box can match + local lo_x = floor((x1 < x2 and x1 or x2) - half) + local hi_x = ceil((x1 > x2 and x1 or x2) + half) + local lo_y = floor((y1 < y2 and y1 or y2) - half) + local hi_y = ceil((y1 > y2 and y1 or y2) + half) + + if lo_x < left then lo_x = left end + if hi_x > right then hi_x = right end + if lo_y < top then lo_y = top end + if hi_y > bottom then hi_y = bottom end + + for ty = lo_y, hi_y do + local row = rows[ty] + local py = ty + 0.5 + for tx = lo_x, hi_x do + if row == nil or row[tx] ~= true then + if dist(tx + 0.5, py, x1, y1, x2, y2) < half then + if row == nil then + row = {} + rows[ty] = row + end + if mirror or row[tx] == nil then + row[tx] = mirror + end + end + end + end + end + end + end + + -- Emit in scan order so the result never depends on hat iteration order. + local out = {} + for ty = top, bottom do + local row = rows[ty] + if row then + for tx = left, right do + local m = row[tx] + if m ~= nil then + out[#out + 1] = { x = tx, y = ty, mirror = m } + end + end + end + end + return out +end + +return Bands diff --git a/mod/surface.lua b/mod/surface.lua new file mode 100644 index 0000000..0c0dce9 --- /dev/null +++ b/mod/surface.lua @@ -0,0 +1,57 @@ +-- Per-surface geometry, derived once at creation and stored. +-- +-- Startup settings can change between sessions. Storing the numbers the +-- terrain was actually built with means a later change affects only NEW +-- surfaces; existing ones keep their geometry and never seam. This is the one +-- idea worth taking from Hextorio's continuous_geometry flag. + +local Config = require("mod.config") +local planets = require("mod.planets") + +local Surface = {} + +--- Name of the planet palette for this surface, or nil if it is not tiled. +local function planet_of(surface) + -- Space platforms are never tiled, and neither is any surface we have no + -- palette for -- including modded planets, which fail open to vanilla. + if surface.platform then return nil end + if planets[surface.name] then return surface.name end + return nil +end + +function Surface.is_tiled(surface) + return planet_of(surface) ~= nil +end + +local function read_setting(name, default) + local s = settings.startup[name] + if s == nil then return default end + return s.value +end + +--- Derive and store this surface's geometry. Idempotent. +function Surface.init(surface) + local planet = planet_of(surface) + if not planet then return nil end + + storage.surfaces = storage.surfaces or {} + local existing = storage.surfaces[surface.index] + if existing then return existing end + + local g = Config.geometry( + read_setting("hattorio-hat-size-" .. planet, Config.DEFAULT_SIZE), + read_setting("hattorio-band-width-" .. planet, Config.DEFAULT_BAND)) + g.planet = planet + g.tile = "hattorio-band-" .. planet + g.mirror_tile = "hattorio-band-" .. planet .. "-mirror" + + storage.surfaces[surface.index] = g + return g +end + +function Surface.get(surface) + storage.surfaces = storage.surfaces or {} + return storage.surfaces[surface.index] or Surface.init(surface) +end + +return Surface diff --git a/spec/bands_spec.lua b/spec/bands_spec.lua new file mode 100644 index 0000000..82c6e2d --- /dev/null +++ b/spec/bands_spec.lua @@ -0,0 +1,72 @@ +local Ti = require("hat.tiling") +local Config = require("mod.config") +local Bands = require("mod.bands") + +local function setup(size, band) + local g = Config.geometry(size, band) + return Ti.new({ unit = g.unit, depth = g.depth }), g +end + +describe("band rasterisation", function() + it("returns band tiles inside the requested area only", function() + local t, g = setup(26, 2) + local area = { left = -40, top = -40, right = 40, bottom = 40 } + local tiles = Bands.for_area(t, g, area) + assert.is_true(#tiles > 0) + for _, p in ipairs(tiles) do + assert.is_true(p.x >= area.left and p.x < area.right, "x " .. p.x) + assert.is_true(p.y >= area.top and p.y < area.bottom, "y " .. p.y) + end + end) + + it("covers a plausible fraction of the area", function() + local t, g = setup(26, 2) + local n = #Bands.for_area(t, g, { left=-64, top=-64, right=64, bottom=64 }) + local frac = n / (128 * 128) + assert.is_true(frac > 0.10 and frac < 0.40, "fraction " .. frac) + end) + + it("marks some tiles as mirror and most not", function() + local t, g = setup(26, 2) + local tiles = Bands.for_area(t, g, { left=-96, top=-96, right=96, bottom=96 }) + local m = 0 + for _, p in ipairs(tiles) do if p.mirror then m = m + 1 end end + assert.is_true(m > 0, "no mirror tiles") + assert.is_true(m < #tiles / 2, "too many mirror tiles") + end) + + it("is deterministic", function() + local t, g = setup(26, 2) + local a = { left = 0, top = 0, right = 32, bottom = 32 } + local one, two = Bands.for_area(t, g, a), Bands.for_area(t, g, a) + assert.are.equal(#one, #two) + for i = 1, #one do + assert.are.equal(one[i].x, two[i].x) + assert.are.equal(one[i].y, two[i].y) + assert.are.equal(one[i].mirror, two[i].mirror) + end + end) + + it("agrees across a chunk boundary", function() + -- a band must not break where two chunks meet: every chunk derives its + -- tiles from the tiling, never from a neighbour's tiles + local t, g = setup(26, 2) + local l = Bands.for_area(t, g, { left=-32, top=0, right=0, bottom=32 }) + local r = Bands.for_area(t, g, { left=0, top=0, right=32, bottom=32 }) + local b = Bands.for_area(t, g, { left=-32, top=0, right=32, bottom=32 }) + assert.are.equal(#l + #r, #b) + end) + + it("makes a wider band cover more tiles", function() + local t1, g1 = setup(26, 1) + local t3, g3 = setup(26, 3) + local a = { left = -48, top = -48, right = 48, bottom = 48 } + assert.is_true(#Bands.for_area(t3, g3, a) > #Bands.for_area(t1, g1, a)) + end) + + it("leaves cells connected: no area is entirely band", function() + local t, g = setup(26, 2) + local a = { left = -64, top = -64, right = 64, bottom = 64 } + assert.is_true(#Bands.for_area(t, g, a) < 128 * 128) + end) +end) diff --git a/tools/bench.lua b/tools/bench.lua index 1b9445c..67dc94c 100644 --- a/tools/bench.lua +++ b/tools/bench.lua @@ -20,6 +20,26 @@ for i = 1, CHUNKS do total = total + #t:hats_in_box({ left=cx, top=cy, right=cx+32, bottom=cy+32 }) end local ms = 1000 * (os.clock() - start) / CHUNKS -print(string.format("%d chunks: %.3f ms/chunk, %.2f hats/chunk", CHUNKS, ms, total/CHUNKS)) +print(string.format("descent: %d chunks, %.3f ms/chunk, %.2f hats/chunk", + CHUNKS, ms, total/CHUNKS)) assert(ms < 50, "descent too slow for chunk generation: " .. ms .. " ms") + +-- Full per-chunk cost, which is what terrain generation actually pays. +-- Rasterising each edge into its own bounding box rather than testing every +-- tile against every edge took this from 39.7 ms/chunk to about 1.3. +local Config = require("mod.config") +local Bands = require("mod.bands") +local g = Config.geometry(26, 2) +local bt = Ti.new({ unit = g.unit, depth = g.depth }) + +local bstart = os.clock() +local tiles = 0 +for i = 1, CHUNKS do + local cx = (i % 20) * 32 + local cy = math.floor(i / 20) * 32 + tiles = tiles + #Bands.for_area(bt, g, { left=cx, top=cy, right=cx+32, bottom=cy+32 }) +end +local bms = 1000 * (os.clock() - bstart) / CHUNKS +print(string.format("bands: %.3f ms/chunk, %.0f band tiles/chunk", bms, tiles/CHUNKS)) +assert(bms < 10, "band rasterisation too slow for chunk generation: " .. bms .. " ms") print("OK") From 285da148ed7eb9c38f9427d5b096c8a6d78690a1 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:56:57 +0200 Subject: [PATCH 06/17] feat(mod): paint bands at chunk generation and offset ore richness --- mod/terrain.lua | 103 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 mod/terrain.lua diff --git a/mod/terrain.lua b/mod/terrain.lua new file mode 100644 index 0000000..27d89de --- /dev/null +++ b/mod/terrain.lua @@ -0,0 +1,103 @@ +-- Terrain generation: paint bands into each chunk as it is generated. +-- +-- Painting once at generation means zero per-tick cost. Nothing about the +-- bands is persisted beyond the tiles themselves and the per-surface geometry: +-- the tiling is derived from that geometry and rebuilt on load. + +local Ti = require("hat.tiling") +local Config = require("mod.config") +local Surface = require("mod.surface") +local Bands = require("mod.bands") + +-- Derived state, keyed by surface index. Deliberately NOT in storage: it is +-- reconstructible from the stored geometry, and a tiling holds closures. +local tilings = {} + +local function tiling_for(surface) + local g = Surface.get(surface) + if not g then return nil, nil end + local t = tilings[surface.index] + if not t then + t = Ti.new({ unit = g.unit, depth = g.depth }) + tilings[surface.index] = t + end + return t, g +end + +local function area_of(event_area) + return { + left = event_area.left_top.x, top = event_area.left_top.y, + right = event_area.right_bottom.x, bottom = event_area.right_bottom.y, + } +end + +--- Paint the band tiles of one chunk. +local function paint_chunk(surface, area) + local t, g = tiling_for(surface) + if not t then return end + + local band = Bands.for_area(t, g, area) + if #band == 0 then return end + + local tiles = {} + for i = 1, #band do + local p = band[i] + -- Never pave water: a band across a lake would fill it, and vanilla + -- shore transitions would be lost. + -- + -- Factorio's LuaObject methods take dot notation and no self, so this is + -- surface.get_tile(x, y) -- caching the function and passing `surface` + -- would silently put the surface where x belongs. + if not surface.get_tile(p.x, p.y).collides_with("water_tile") then + tiles[#tiles + 1] = { + name = p.mirror and g.mirror_tile or g.tile, + position = { p.x, p.y }, + } + end + end + if #tiles == 0 then return end + + -- remove_colliding_entities = false: ore, trees and rocks already placed on + -- a band stay put. Ore there is simply out of reach of a 3x3 drill, which + -- the richness multiplier compensates for. + surface.set_tiles(tiles, true, false, false, false) +end + +--- Raise ore richness to offset what the bands put out of reach. +-- +-- Applied per chunk as it generates, so it never touches ore a player has +-- already mined. Infinite resources are skipped: their yield is a percentage, +-- and scaling that would be wrong. +local function compensate_resources(surface, area) + local g = Surface.get(surface) + if not g then return end + local mult = Config.richness_multiplier(g.size, g.band) + if mult <= 1.0 then return end + + local found = surface.find_entities_filtered({ + area = { { area.left, area.top }, { area.right, area.bottom } }, + type = "resource", + }) + for i = 1, #found do + local e = found[i] + if e.valid and not e.prototype.infinite_resource then + e.amount = math.floor(e.amount * mult) + end + end +end + +script.on_event(defines.events.on_chunk_generated, function(event) + if not Surface.is_tiled(event.surface) then return end + local area = area_of(event.area) + paint_chunk(event.surface, area) + compensate_resources(event.surface, area) +end) + +script.on_event(defines.events.on_surface_created, function(event) + local surface = game.surfaces[event.surface_index] + if surface then Surface.init(surface) end +end) + +-- Derived state only; rebuilt lazily on next use. +script.on_load(function() tilings = {} end) +script.on_configuration_changed(function() tilings = {} end) From 14702200110edb6b1967ff96645bbc7f10a859b9 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 13:58:47 +0200 Subject: [PATCH 07/17] feat(mod): thumbnail and distributable zip Packaging is Python rather than a shell script calling zip(1), which is not installed everywhere. A test asserts the archive shape: one _/ directory with info.json at its root, the tiling core and its data present, and no spec, docs, tools or wiki files. --- .gitignore | 1 + Makefile | 6 +++- thumbnail.png | Bin 0 -> 20984 bytes tools/package.py | 66 +++++++++++++++++++++++++++++++++++++++ tools/tests/test_emit.py | 19 +++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 thumbnail.png create mode 100644 tools/package.py diff --git a/.gitignore b/.gitignore index 453f897..3e9996a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__/ .venv/ luacov.* .wiki-tmp/ +build/ diff --git a/Makefile b/Makefile index 810866a..8958b8c 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else RUN_BUSTED := $(BUSTED) endif -.PHONY: all test pytest lint data check preview wiki +.PHONY: all test pytest lint data check preview wiki package all: check @@ -42,6 +42,10 @@ lint: check: pytest test lint +## build the distributable mod zip +package: + python3 tools/package.py + ## publish wiki/ to the GitHub wiki (needs one page created in the UI first) wiki: @rm -rf .wiki-tmp diff --git a/thumbnail.png b/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..c5b0ca82c77d46678e947b4ee2d4b2e583d6b87a GIT binary patch literal 20984 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 O0000_ +with info.json at its root. Development files -- tests, the offline pipeline, +docs, wiki source, CI config -- never ship. + +Uses zipfile rather than the zip(1) binary, which is not installed everywhere. +""" +from __future__ import annotations + +import json +import pathlib +import zipfile + +ROOT = pathlib.Path(__file__).resolve().parents[1] + +# Everything the game needs, and nothing else. +SHIP = [ + "info.json", "changelog.txt", "thumbnail.png", + "data.lua", "data-final-fixes.lua", "settings.lua", "control.lua", + "LICENSE", "THIRD_PARTY.md", + "prototypes", "mod", "hat", "data", "locale", +] + +EXCLUDE_SUFFIX = {".pyc"} +EXCLUDE_DIR = {"__pycache__", "tests"} + + +def files_under(path: pathlib.Path): + if path.is_file(): + yield path + return + for p in sorted(path.rglob("*")): + if p.is_dir(): + continue + if p.suffix in EXCLUDE_SUFFIX: + continue + if any(part in EXCLUDE_DIR for part in p.parts): + continue + yield p + + +def main(): + info = json.loads((ROOT / "info.json").read_text()) + stem = f"{info['name']}_{info['version']}" + out_dir = ROOT / "build" + out_dir.mkdir(exist_ok=True) + out = out_dir / f"{stem}.zip" + if out.exists(): + out.unlink() + + count = 0 + with zipfile.ZipFile(out, "w", zipfile.ZIP_DEFLATED) as z: + for entry in SHIP: + src = ROOT / entry + if not src.exists(): + raise SystemExit(f"missing from the working tree: {entry}") + for f in files_under(src): + z.write(f, f"{stem}/{f.relative_to(ROOT)}") + count += 1 + + print(f"built {out.relative_to(ROOT)} ({count} files, {out.stat().st_size // 1024} KB)") + + +if __name__ == "__main__": + main() diff --git a/tools/tests/test_emit.py b/tools/tests/test_emit.py index 9de2c79..ae87dc2 100644 --- a/tools/tests/test_emit.py +++ b/tools/tests/test_emit.py @@ -1,3 +1,4 @@ +import json import math import pathlib import re @@ -55,3 +56,21 @@ def test_golden_fixture_is_written(): p = ROOT / "spec" / "fixtures" / "golden_depth3.lua" assert p.exists() assert len(p.read_text().splitlines()) > 1000 + + +def test_mod_package_has_the_right_shape(): + """The zip must hold one _/ directory with info.json at its + root, the tiling core and its data, and no development files.""" + import zipfile + subprocess.run([sys.executable, "tools/package.py"], cwd=ROOT, check=True) + info = json.loads((ROOT / "info.json").read_text()) + stem = f"{info['name']}_{info['version']}" + z = zipfile.ZipFile(ROOT / "build" / f"{stem}.zip") + names = z.namelist() + + assert f"{stem}/info.json" in names + assert f"{stem}/hat/tiling.lua" in names + assert f"{stem}/data/hat_rules.lua" in names + assert f"{stem}/control.lua" in names + for forbidden in ("/spec/", "/docs/", "/tools/", "/wiki/", "__pycache__"): + assert not any(forbidden in n for n in names), forbidden From b92f084c4bc1612b9130421c7444e105d4d0bb6c Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 14:22:59 +0200 Subject: [PATCH 08/17] feat(mod): band style setting, outline overlay and richness override Bands now clone deepwater rather than stone-path: its transitions are authored for liquid meeting land, and it carries the only animated shader in the base game. Three styles select the effect over it -- liquid (animated), void (none), rift (lava, Space Age only, falls back to liquid and logs when absent). Adds the two settings that genuinely belong outside startup: a per-player cell outline overlay, and a map-scoped ore richness override where 0 means compensate automatically. --- docs/preview/band-colours.png | Bin 0 -> 35609 bytes locale/en/hattorio.cfg | 12 ++++ mod/outline.lua | 111 ++++++++++++++++++++++++++++++++++ mod/planets.lua | 31 +++++----- mod/terrain.lua | 38 ++++++++++-- prototypes/settings.lua | 37 ++++++++++++ prototypes/tiles.lua | 69 +++++++++++++++------ tools/render_bands.lua | 10 +-- 8 files changed, 264 insertions(+), 44 deletions(-) create mode 100644 docs/preview/band-colours.png create mode 100644 mod/outline.lua diff --git a/docs/preview/band-colours.png b/docs/preview/band-colours.png new file mode 100644 index 0000000000000000000000000000000000000000..64c5dc1f214a66a85b1c68757124ef00409aa4f7 GIT binary patch literal 35609 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%0DG&`ZA*iM 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 literal 0 HcmV?d00001 diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index d000b07..b8fa464 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -10,7 +10,19 @@ hattorio-band-width-gleba=Band width (Gleba) hattorio-hat-size-aquilo=Cell size (Aquilo) hattorio-band-width-aquilo=Band width (Aquilo) +hattorio-band-style=Band appearance +hattorio-richness-override=Ore richness override +hattorio-show-outline=Show cell outlines + +[string-mod-setting] +hattorio-band-style-liquid=Dark liquid (animated) +hattorio-band-style-void=Void (flat black) +hattorio-band-style-rift=Glowing rift (needs Space Age) + [mod-setting-description] +hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes. Void is a flat, still hole. Glowing rift uses the lava shader and needs Space Age, falling back to dark liquid without it. +hattorio-richness-override=Multiplier applied to ore in newly generated chunks, replacing the automatic compensation for ore the bands put out of reach. Leave at 0 to compensate automatically from your cell size and band width. +hattorio-show-outline=Draw a line along the centre of each band. Affects only your own view. hattorio-hat-size-nauvis=Distance from a cell's centre to its furthest corner, in tiles. Larger cells give more room and let larger blueprints be reused. hattorio-band-width-nauvis=Width of the unbuildable band between cells, in tiles. Keep below underground belt reach or cells become isolated. hattorio-hat-size-vulcanus=Distance from a cell's centre to its furthest corner, in tiles. diff --git a/mod/outline.lua b/mod/outline.lua new file mode 100644 index 0000000..18b754a --- /dev/null +++ b/mod/outline.lua @@ -0,0 +1,111 @@ +-- Optional per-player overlay: a line down the centre of each band. +-- +-- Per player because it is pure presentation -- one player turning it on +-- cannot change the terrain or what anyone else sees. The render objects are +-- created with `players = {index}` so they are genuinely private. +-- +-- Render objects are game state, so they are tracked in `storage` rather than +-- a module local: a module local would be lost across save/load and the +-- objects would leak, accumulating every time the save was reloaded. + +local Ti = require("hat.tiling") +local G = require("hat.geometry") +local Surface = require("mod.surface") + +local Outline = {} + +-- How far around the player to draw, and how far they may move before it is +-- redrawn. Redrawing on every move would be wasteful; never redrawing would +-- leave the overlay behind. +local RADIUS = 64 +local MOVE_BEFORE_REDRAW = 24 + +local tilings = {} + +local function tiling_for(surface) + local g = Surface.get(surface) + if not g then return nil, nil end + local t = tilings[surface.index] + if not t then + t = Ti.new({ unit = g.unit, depth = g.depth }) + tilings[surface.index] = t + end + return t, g +end + +local function clear(player_index) + local state = storage.outlines and storage.outlines[player_index] + if not state then return end + for i = 1, #state.objects do + local o = state.objects[i] + if o.valid then o.destroy() end + end + storage.outlines[player_index] = nil +end + +Outline.clear = clear + +local function draw(player) + local surface = player.surface + local t, g = tiling_for(surface) + if not t then return end + + local pos = player.position + local hats = t:hats_in_box({ + left = pos.x - RADIUS, top = pos.y - RADIUS, + right = pos.x + RADIUS, bottom = pos.y + RADIUS, + }) + + local objects = {} + for i = 1, #hats do + local edges = G.edges(hats[i].xf, g.unit) + for j = 1, #edges do + local e = edges[j] + objects[#objects + 1] = rendering.draw_line({ + color = { 0.55, 0.62, 0.72, 0.5 }, + width = 1.5, + from = { e[1], e[2] }, + to = { e[3], e[4] }, + surface = surface, + players = { player.index }, + draw_on_ground = true, + }) + end + end + + storage.outlines = storage.outlines or {} + storage.outlines[player.index] = { objects = objects, x = pos.x, y = pos.y } +end + +--- Bring one player's overlay up to date. Cheap when nothing has changed. +function Outline.refresh(player) + if not (player and player.valid and player.connected) then return end + + local on = settings.get_player_settings(player)["hattorio-show-outline"] + if not (on and on.value) then + clear(player.index) + return + end + if not Surface.is_tiled(player.surface) then + clear(player.index) + return + end + + local state = storage.outlines and storage.outlines[player.index] + if state then + local dx = player.position.x - state.x + local dy = player.position.y - state.y + if dx * dx + dy * dy < MOVE_BEFORE_REDRAW * MOVE_BEFORE_REDRAW then + return + end + end + + clear(player.index) + draw(player) +end + +function Outline.reset() + tilings = {} +end + +return Outline diff --git a/mod/planets.lua b/mod/planets.lua index 5743416..29c8b36 100644 --- a/mod/planets.lua +++ b/mod/planets.lua @@ -1,21 +1,20 @@ -- Which surfaces get tiled, and what the band looks like there. -- --- base_tile is a vanilla tile whose graphics and transitions are cloned; only --- the name, colours and collision mask change. Cloning is what makes this --- affordable: authoring tile transition graphics from scratch is the --- expensive part, and vanilla has already done it. +-- All three band styles clone `deepwater`. That is not laziness: its +-- transitions are authored for "liquid meets land", which is exactly the edge +-- a rift needs, and it is the only base tile carrying an animated shader. +-- The styles differ only in the effect and the colours laid over it. -- --- NOTE: map_color differs per planet but the in-world graphics do not yet, --- since they come from base_tile. Real per-planet art is a later job. +-- liquid the water shader, recoloured dark -- it moves and oozes +-- void no effect at all -- a flat, still hole in the world +-- rift Vulcanus's lava shader -- glows, REQUIRES Space Age +-- +-- effect_color is the body of the surface; effect_color_secondary is the +-- highlight the shader catches at the edges. return { - nauvis = { base_tile = "stone-path", map_color = { 43, 47, 54 }, - mirror_color = { 20, 121, 201 } }, - vulcanus = { base_tile = "stone-path", map_color = { 38, 26, 24 }, - mirror_color = { 176, 64, 32 } }, - fulgora = { base_tile = "stone-path", map_color = { 46, 38, 56 }, - mirror_color = { 150, 96, 200 } }, - gleba = { base_tile = "stone-path", map_color = { 34, 46, 32 }, - mirror_color = { 120, 170, 70 } }, - aquilo = { base_tile = "stone-path", map_color = { 48, 56, 62 }, - mirror_color = { 120, 180, 210 } }, + nauvis = { map_color = { 23, 27, 34 }, mirror_color = { 20, 121, 201 } }, + vulcanus = { map_color = { 29, 16, 11 }, mirror_color = { 176, 64, 32 } }, + fulgora = { map_color = { 30, 19, 48 }, mirror_color = { 150, 96, 200 } }, + gleba = { map_color = { 14, 26, 19 }, mirror_color = { 120, 170, 70 } }, + aquilo = { map_color = { 26, 32, 38 }, mirror_color = { 120, 180, 210 } }, } diff --git a/mod/terrain.lua b/mod/terrain.lua index 27d89de..45a5c39 100644 --- a/mod/terrain.lua +++ b/mod/terrain.lua @@ -8,6 +8,7 @@ local Ti = require("hat.tiling") local Config = require("mod.config") local Surface = require("mod.surface") local Bands = require("mod.bands") +local Outline = require("mod.outline") -- Derived state, keyed by surface index. Deliberately NOT in storage: it is -- reconstructible from the stored geometry, and a tiling holds closures. @@ -71,8 +72,14 @@ end local function compensate_resources(surface, area) local g = Surface.get(surface) if not g then return end - local mult = Config.richness_multiplier(g.size, g.band) - if mult <= 1.0 then return end + -- A map setting of 0 means "compensate automatically"; anything else + -- replaces the derived value outright. + local override = settings.global["hattorio-richness-override"] + local mult = override and override.value or 0 + if mult <= 0 then + mult = Config.richness_multiplier(g.size, g.band) + end + if mult == 1.0 then return end local found = surface.find_entities_filtered({ area = { { area.left, area.top }, { area.right, area.bottom } }, @@ -98,6 +105,29 @@ script.on_event(defines.events.on_surface_created, function(event) if surface then Surface.init(surface) end end) +script.on_nth_tick(30, function() + for _, player in pairs(game.connected_players) do + Outline.refresh(player) + end +end) + +script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) + if event.setting == "hattorio-show-outline" and event.player_index then + Outline.clear(event.player_index) + end +end) + +script.on_event(defines.events.on_player_changed_surface, function(event) + Outline.clear(event.player_index) +end) + -- Derived state only; rebuilt lazily on next use. -script.on_load(function() tilings = {} end) -script.on_configuration_changed(function() tilings = {} end) +script.on_load(function() + tilings = {} + Outline.reset() +end) + +script.on_configuration_changed(function() + tilings = {} + Outline.reset() +end) diff --git a/prototypes/settings.lua b/prototypes/settings.lua index 6f10cab..d74c013 100644 --- a/prototypes/settings.lua +++ b/prototypes/settings.lua @@ -33,4 +33,41 @@ for planet, _ in pairs(planets) do } end +-- Band appearance. Startup, because it selects which tile prototypes exist. +-- +-- "rift" needs Space Age for Vulcanus's lava shader; prototypes/tiles.lua +-- falls back to "liquid" and logs if it is unavailable, rather than refusing +-- to load. +settings_list[#settings_list + 1] = { + type = "string-setting", + name = "hattorio-band-style", + setting_type = "startup", + default_value = "liquid", + allowed_values = { "liquid", "void", "rift" }, + order = "c[style]", +} + +-- Map-scoped, because ore is shared between everyone on it. 0 means "use the +-- automatic compensation derived from cell size and band width"; any other +-- value replaces it outright. +settings_list[#settings_list + 1] = { + type = "double-setting", + name = "hattorio-richness-override", + setting_type = "runtime-global", + default_value = 0.0, + minimum_value = 0.0, + maximum_value = 10.0, + order = "d[richness]", +} + +-- Per player, because it is pure presentation: one player having the overlay +-- on cannot affect what anyone else sees or what the terrain is. +settings_list[#settings_list + 1] = { + type = "bool-setting", + name = "hattorio-show-outline", + setting_type = "runtime-per-user", + default_value = false, + order = "e[outline]", +} + data:extend(settings_list) diff --git a/prototypes/tiles.lua b/prototypes/tiles.lua index 23a53cf..4201ca3 100644 --- a/prototypes/tiles.lua +++ b/prototypes/tiles.lua @@ -1,37 +1,66 @@ local planets = require("mod.planets") --- A TilePrototype requires name, type, collision_mask, layer, variants and --- map_color. Cloning a vanilla tile inherits variants, transitions and layer, --- which is the expensive part to author. --- --- walking_speed_modifier is cleared so it falls back to its default of 1: a --- band must not grant the free walking bonus its base tile carries. -local function band_tile(name, base, color) - local src = data.raw.tile[base] +-- Band appearance is a startup setting, so the tiles differ by what the +-- player chose. All three styles clone `deepwater`: its transitions are +-- authored for liquid meeting land, which is the edge a rift wants, and it +-- carries the only animated shader in the base game. +local STYLE = settings.startup["hattorio-band-style"].value + +-- The glowing style needs Vulcanus's lava shader, which only Space Age ships. +-- Fall back rather than fail to load, and say so in the log. +if STYLE == "rift" and not mods["space-age"] then + log("hattorio: band style 'rift' needs Space Age for the lava shader; " .. + "falling back to 'liquid'") + STYLE = "liquid" +end + +-- Body and highlight colours per style. The body is deliberately near-black +-- in every case; the highlight is what distinguishes them. +local STYLE_COLOURS = { + liquid = { body = { 23, 27, 34 }, highlight = { 46, 58, 69 }, effect = "water" }, + void = { body = { 8, 9, 12 }, highlight = { 8, 9, 12 }, effect = nil }, + rift = { body = { 26, 12, 8 }, highlight = { 167, 59, 27 }, effect = "lava-2" }, +} + +local style = STYLE_COLOURS[STYLE] or STYLE_COLOURS.liquid + +local function band_tile(name, map_color) + local src = data.raw.tile["deepwater"] if not src then - error("hattorio: base tile '" .. base .. "' does not exist") + error("hattorio: base tile 'deepwater' does not exist") end local t = table.deepcopy(src) t.name = name - t.map_color = color - t.walking_speed_modifier = nil - t.minable = nil - t.mined_sound = nil - t.can_be_part_of_blueprint = false - t.autoplace = nil + t.map_color = map_color t.order = "z[hattorio]" t.localised_name = { "tile-name.hattorio-band" } - -- Only the band layer: the tile itself blocks nothing else, so the - -- character and vehicles pass over it. + + -- Only the band layer. The tile blocks nothing else, so the character and + -- vehicles pass over it -- a rift you can step across. t.collision_mask = { layers = { hattorio_band = true } } + + t.effect = style.effect + t.effect_color = style.body + t.effect_color_secondary = style.highlight + + -- Not water: must not feed an offshore pump, must not merge with lakes, + -- must not be minable or appear in blueprints, and must never autoplace. + t.fluid = nil + t.transition_merges_with_tile = nil + t.autoplace = nil + t.minable = nil + t.mined_sound = nil + t.can_be_part_of_blueprint = false + t.walking_speed_modifier = nil + t.factoriopedia_alternative = nil + return t end local tiles = {} for planet, cfg in pairs(planets) do - tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet, cfg.base_tile, cfg.map_color) - tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet .. "-mirror", - cfg.base_tile, cfg.mirror_color) + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet, cfg.map_color) + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet .. "-mirror", cfg.mirror_color) end data:extend(tiles) diff --git a/tools/render_bands.lua b/tools/render_bands.lua index bee95cd..1795a83 100644 --- a/tools/render_bands.lua +++ b/tools/render_bands.lua @@ -15,6 +15,8 @@ local size = tonumber(arg[1]) or 20 local band = tonumber(arg[2]) or 2 local tiles = tonumber(arg[3]) or 200 local out = arg[4] or "bands.svg" +local band_c = arg[5] or "#2b2f36" -- band colour +local land_c = arg[6] or "#7a8b5a" -- surrounding land local unit = Ti.unit_for_hat_size(size) local half = band / 2 @@ -38,8 +40,8 @@ local f = assert(io.open(out, "w")) f:write(string.format('\n', tiles * 4, tiles * 4, lo, lo, tiles, tiles)) -f:write(string.format('\n', - lo, lo, tiles, tiles)) +f:write(string.format('\n', + lo, lo, tiles, tiles, land_c)) local band_tiles = 0 for ty = lo, hi do @@ -59,8 +61,8 @@ for ty = lo, hi do end if on_band then band_tiles = band_tiles + 1 - f:write(string.format('\n', - tx, ty)) + f:write(string.format('\n', + tx, ty, band_c)) end end end From 2a8355bd04734d754aefd29c477aff49d7706347 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 14:32:03 +0200 Subject: [PATCH 09/17] feat(mod): size and band become dropdowns, default 41/3 Playtesting found 26/2 cramped. Both settings are now dropdowns rather than sliders, labelled with the difficulty each implies -- the number alone does not convey it, and the interesting range is four values, not seventy-six. Still per planet. Also replaces the colour comparison sheet: the first one showed four near-blacks as two-tile lines, which were indistinguishable. The new one zooms in, widens the band and shows the shader highlight, which is what actually separates the options. --- README.md | 21 +- docs/design/hattorio-design.md | 28 +- docs/preview/band-colours.png | Bin 35609 -> 22221 bytes docs/preview/bands-41-3.png | Bin 0 -> 16951 bytes docs/preview/bands-41-3.svg | 21971 +++++++++++++++++++++++++++++++ locale/en/hattorio.cfg | 60 +- mod/config.lua | 14 +- mod/surface.lua | 10 +- prototypes/settings.lua | 18 +- spec/config_spec.lua | 50 +- wiki/Settings.md | 34 +- 11 files changed, 22143 insertions(+), 63 deletions(-) create mode 100644 docs/preview/bands-41-3.png create mode 100644 docs/preview/bands-41-3.svg diff --git a/README.md b/README.md index dfc39a3..671b92b 100644 --- a/README.md +++ b/README.md @@ -24,33 +24,34 @@ That single rule changes how you build: default settings there are **624 distinct buildable tile shapes**, and no single one covers more than 1.1% of the map. A layout that fills one cell will not fit the next. -- **Small blueprints still travel.** Anything up to 11×11 at the default +- **Small blueprints still travel.** Anything up to 18×18 at the default settings fits every cell. Assembler clusters are fine; a 30×30 mall is not. - **Every connection between cells is a puzzle.** Belts cross by underground pair, against an edge that is never axis-aligned. The result is a factory that has to be fitted to the ground it sits on. -![Cells rasterised onto Factorio's tile grid](docs/preview/bands-26-2.png) +![Cells rasterised onto Factorio's tile grid](docs/preview/bands-41-3.png) ## Settings Per planet, set at world creation. -| Setting | Default | Range | +| Setting | Default | Choices | |---|---|---| -| Hat size (centre to vertex, tiles) | **26** | 15–90 | -| Band width (tiles) | **2** | 1–6 | +| Cell size (centre to vertex, tiles) | **41** (normal) | 15, 26, 41, 52 | +| Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | +| Band appearance | Dark liquid | liquid, void, glowing rift | Hat size is the dial that matters. It decides how much room you get and how large a blueprint survives: -| size | cell area | unbuildable | reusable blueprint | preview | +| size | difficulty | cell area | reusable blueprint | preview | |---|---|---|---|---| -| 15 | 116 t | 37.4% | ~5×5 | [look](docs/preview/bands-15-2.png) | -| **26** | **347 t** | **22.5%** | **11×11** | [look](docs/preview/bands-26-2.png) | -| 41 | 944 t | 14.6% | 18×18 | [look](docs/preview/bands-41-2.png) | -| 52 | 1,584 t | 11.5% | 23×23 | [look](docs/preview/bands-52-2.png) | +| 15 | very hard | 116 t | ~5×5 | [look](docs/preview/bands-15-2.png) | +| 26 | hard | 347 t | 11×11 | [look](docs/preview/bands-26-2.png) | +| **41** | **normal** | **944 t** | **18×18** | [look](docs/preview/bands-41-3.png) | +| 52 | easy | 1,584 t | 23×23 | [look](docs/preview/bands-52-2.png) | Changing the setting later affects only newly created surfaces; existing ones keep the geometry their terrain was built with, so nothing ever seams. diff --git a/docs/design/hattorio-design.md b/docs/design/hattorio-design.md index 35fa3e6..7ce973f 100644 --- a/docs/design/hattorio-design.md +++ b/docs/design/hattorio-design.md @@ -409,13 +409,27 @@ Per planet, startup: | Setting | Default | Range | |---|---|---| -| `hattorio-hat-size-` | **26** (centre-to-vertex, tiles) | 15-90 | -| `hattorio-band-width-` | **2** | 1-6 | - -Both are genuine player settings, per planet, exactly as Hextorio does -it. 26/2 gives a 347-tile cell where blueprints up to 11x11 -- about -one assembler cluster -- stay reusable while everything larger must be -hand-fitted. Hextorio parity would be size 52; 41 is a middle option. +| `hattorio-hat-size-` | **41** (normal) | 15, 26, 41, 52 | +| `hattorio-band-width-` | **3** (wide) | 1, 2, 3, 4 | +| `hattorio-band-style` | liquid | liquid, void, rift | +| `hattorio-richness-override` (map) | 0 = automatic | 0 - 10 | +| `hattorio-show-outline` (per player) | off | on, off | + +Size and band are **dropdowns**, not sliders: the difficulty of a given +size is not readable from the number, and the interesting range is four +values rather than seventy-six. Each is labelled with the difficulty it +implies -- 15 very hard, 26 hard, 41 normal, 52 easy. + +**41/3 is the default, chosen by playtest.** An earlier 26/2 proved +cramped in practice. 41 gives a 944-tile cell where blueprints up to +18x18 stay reusable -- enough to build in, while every standard layout +still breaks. Band 3 keeps the bands visually substantial, which band 2 +does not at that cell size. + +Only size and band are per planet. Band style is global, and the other +two are scoped by what they affect: richness is map-wide because ore is +shared, and the outline is per player because it is pure presentation +and cannot change what anyone else sees. **Why startup rather than runtime.** The geometry determines terrain, so changing it under an existing save would leave every generated diff --git a/docs/preview/band-colours.png b/docs/preview/band-colours.png index 64c5dc1f214a66a85b1c68757124ef00409aa4f7..63a17963316db9e919536eb8ce6578598c134dbf 100644 GIT binary patch literal 22221 zcmeI4cU)83w(r4)ihu=0MaWjSf~cS(A~m`J*@A#k5CtJtIs~Mb5Z!J;K|n=Bq=d2& z1Obr}X^Dt{AP{;<2$5bw2qh4bkmSwiE^0XCo_pWB=Y2lMKk_kIthMGGbFQ)G_|5Ma z_f8w@i>=1E#a5w?CQm72bC!-=sg&(c$2)|Kb%cFUg&ZCajA?mkud z+v}A^WtY1it-g}1XnpOTly6(##;#{l2X5CbxcCv64dRFT+-(}-)P@6j6xW7f_aS9; ze|R6j-h;bo1_#V+{t9LO<;V91R zF2g6K)F(ox&mI|}cH>dp87XU@%WQvZztPl_k-NhnP;73lTi5IBda!+PY(U-9gKR2> zoEmLyL9hXrO+yE=u18z7_jWaC&`NW!Rv$TXM0>XNth2XQC#O;h9(UBO?~Pl3xaRbS zsJ-Te&V@GyYnO^2y~Lazq@rI{RNhu|FeF2&g4@Gf1bFZcu#kO;wR^Q*^PA%w=4VWy? zG|%aI0N*C04(sUd?(XVGD9S_pMv{OfM`D+8oHR^6DA{R^N8u(py4-BOE!~R(@MVV#}d{R~7u*cEa>! z+Im-NIF-_{>>l6Ycrh`t`uh43@a^^WkA!e$xWWCDkAP=Z-1fK!=A_Gn5d8brX!){0nF<@RL7*8?dV`o z7*z%gQTY5%#2LE(*-&upI@>C5pbZmdQ zhh&FrS;6fSY7|5DHFG1IdMkpJ&c8~O54j^M)$mLuQ$B<}QG%e4G#Mer22NUo84C_< z(sKjNj@YL5n#vU~p_R@wsFFO+!X({?8_GWYB5E*W#5Zz_2^1K`TuN^$7`l+;Ocs}p z*nF@rNZLg{g#Wqu;1yk>P02*DqkZ0T(M@;dg$DgKFdqI3^Hbg=&kis|*ejJGHIZ^T z76lntY+%*>EtW1tgZH;6yOd5MB5?9H z|9k3iK;Jz%0;1HREkn8CXn#S6we(@T#=00~Oy`x*=7`P8Hgzx35-c)C&CIO|CUS+3 zsp{u1iAQmnpg$DQkZz*Ll4C}cNZ>ULKD|c$O@D#EsNp?LPie7Tk-kc~;Tf?eg*T7g zeqD2u-}{z`9L8$zbBp>Kc?`sQw#weOG z<{3V4=(jM{9x&^HBAVdL53o(pmj_AU+<`Sam$a0v;@|rF3 z6^l9HmWUE$1j&o!KaijY>n`-Eku8xxqex;UBSia*6Rb6?37{3(w_4Dh&f?l4LNvMscE2(N%kxq&F$=Wol}wWD_Zfc1E&tqC~93 zd1}4V`N6fYn>QToo%grp%7>`rL$QkI=QP531HbYvdgYjBCeNReOzdudc|rLKo}{fl_sSfObZ*y+p39-}y3QJ_ z&+im@X-%TXhhmAqE7+qon2kEx)9hebUu6BWWOz%kzJ(4gd8Q_rLkQ*TRJ4 zmG=JKrN_T{5B|=g>A%wQ+0pRUUm-{uG_=-hU{#5v*_cdxz;KdO{n1QSlzQ3s6Ys;N zbx5a2=`%|wT4q$N)R^%!Vw#Dl>7nmUd0Wpksurz07S)tx9GHH#wWY9gnxn;49?|;V z53dMKe4fKHWu}}zUGq{`xRyExucNu**<{S%d{vV)7De*Yf1fGdrOVLTcJJ2%>b?UcrO7fg969xD>F z+dN6;ahj*lYBTruv|wEcd#Dg~_vVwh`L_Ml@c~siE*HSttO`-W(I8Fr(dvDv1!k)W zRxfo&k7yl^Yz~93J$Fn0yuIJx-nQbYW#_o-p0u{LS^4)Xg(mwM2UNX@gNOM@L6?@QO9_6vA^juDG}uL;n0n zJ64}|$=Z*y+LKM`5ea1A{UhW#5l;h3AHxN0wwL!FbG(&4LKF5U8o|0{)y#YAZhaxK3{v{s$ zEgK@3V|85;P*$)^A<-*M21C2XPLVUN^@^i2^Ze%-ncH_5q*4wjoUw=RYIiR_w6jG= zq&w`YKFRafHt&0>5bgj$DgNLKu!enmlQk+8n6?J=5j6jJAZFpANy3d2YTm!?4cPrx zgz&*Ox%I(Z#lENmIj!zG2jijcgAp=Oy}pT-=kNU$KTOBSBTsoFpIj)EIKFpMxpXer zv~cFM5!rL8Gnh`_IPx`{j?bE`HzQo%k})4}#!>Ka6K1wHI=8hU-4NXAIPLZNZ$p(s z=XK3Sv(-`vTVyic=I)e3Yrp@_FN4QJMcwwAUn;Ta<~o%RiyVD_0CwGYy|2|siQw&p z%ibPX*H$ig)~lp|t}PyycKu}=e)M$h)k=AhX!&G?3*VaKm34NuP<)sqgx3&jPxW? z@@AVCbsf3Su)D%6t_s-YZjbpY_zRn-sgwZN)^CRaulvHIZ2I0F>320Ef==tL-xir) z8ostCKRn&CFZl$M@T5mZ<_7nLxL|umu3pqhi%8Ik)HU5Dc+J)agc#?8>dOW%3%1i> zc)ebfyM=@8A<-ibqP~lQ%Xeg4P!W6g*!EHSvxd!&ZwVe(2sf0Bn$djv<1g8-{u=IM zy26R2{JE~IwM^?hSSxP zx`*PWWv2TU>EFom`xnbrj;ISBq95d+BQAKLHGR%p>4tF1G9ESt6R8SMmtUk!3RwB9u`L?O$zOYfFGKbV=F^fBgQdDK)#-OIo3GCWR#0)L z-pSyt&3DhPJ7yyIq%1?=(US~%T}Kdl!KJlE88P1r>tE!V&>@(FdR)JlYBPM_Z*aX{ zJi13#{l{qaD@(thL2W9ks@~I+1EczB_%$1|rg2r7wU2EVGR&>p(;%qyNPTb4u+B%H zee}B}h$1(n#CPY9#FXz4Dt>nf<|(m>ua%U>i~bjNls~2b{k$%w$B*LHUnS%AeqAsm za6SUH(4TC%AC6t9-J28eF*s<=me<%a8+-z(ELd3R7KV3bV!KX|CcRNd4^|44hiVz` z?t|6al>O)<$-@PlZg2WpA=5r$(d`vt>g@469HWd@MsGHZ+-d@fE?NG1W;fmv^NSh zMxyVzfOy|TbLxG*eM>lqP8_p~Z05$hBIQgJL#~kgLz3+r90))#$u65CtlF?U&VQl^ z+*4!x6?~yj3dB}{Y%0)z;J84P)=j>KBBS92v<|*ba>Ut!XmXJe1LZOCh}Si{p{%Be zo?gUiA<|2(jB}Y6mB3=L_%ZZ7xdeIA48buc3y2FOe-?uVH#b*xXiW!!P=qY0y|O7q zAI1C#*PiRV3c~0iAO;m`%{Vw}uDvfWZ^jE7yyX4_LgWnIV{Y60Os zkO|JcJb%!4dcZ96Tw6y69>_-@_hT+H5}GeH38)IbqpKi5ALB5{D4<>O^$TJ;)g7

18E-dWu8soY_qL->8q*4Ff5mvXRI|E*WP|E$8Z-v4wpWV$j#MH zC>?w4>rcb5bLC{4T3Z9yS>B|=$p+*cne+6XJ`f}x>3J)$W}AEcCp531d-;=?VpYND zWU|mCO^*&4+QrwWj_(b|xC_^2705qPEW*f#koRGSZxfx%Dxl8()yd$ocyqN$OJgFR zItO8WLTuvM!X$Y`bncTUPm<*~k^MTd{l`ruD<`$QiaG5irI(^~z@j{>U{EZT=-6J` z)7!*i;jsGW%{y|y1d$JcmQ4<~KY8Mf;HsiAy`!%oN|arEl^_SjORDhR7jh+{ zEJ(|TH!VVgW2>!8N||^o*-{j<0*88;Khf>{z5($g-NP$+L!tW0dK>haFHhL_qS5!>@7$hHJG6_n-6?aww3js9O1}vIai%2 zts0w1Gc}P_JlBQ-aG@QbAzt0{EgIeKK-Wf8 zd`Id}cjb?z5RaHy<8%M#1e3J!h^VX4H@6dAd=8;w=Bw>I!5(i*s}-v0fJ>?1u5 zASf1II>wtGHpfoZgg!?HR;FPO6moRr;?(?_j2K)>ZXSZy#*r4=I$ub{waB1cr zwZBXxRT?~YY0**MLz-WHd85y-QP0h>XR@SCE)7C1pR}@anS!M>__E?s~c z(W$^N=ZZQc^>XR${ zU!+|tL{lJ&w!wcKa}_4JQ+)n3tQ8zD4@c-0lHsa}p}LZZzbvrtQ%*osrpej!b!-fq z_w1aC=j*``IrwJOJK^$h90v~#7}VHMGMnxgjR&s+=Y_j@?PXTQQ%Z8Efk&c&bMqDW z0M}|%w@cc7jV^9}3O0)BGhvJEsKCL+*1;64f?*F0*b2D`Em2!yWYs^pY||La-uV)1 zxQ`Ps(HEH(kZ(Wx4A@gi9c8`6>$ko9Hfgd-pj?WZRFu1Z@jKhEgnO`b7qf@fomn+w zprtC=!c;crcEYwKh!o%X+I6AtHCKCd*v4FEugzZqt#CyBd|gALjc%pdSS1**n(}^x zrCL2n3c<@2EOHoWnG>+Zkb1w^l-s$Et|%DFm_|)JQ_hHQN?N^5OGGm1jGO*&^K)dlBEYDMjbBtYAAqm<=;kOH1|Iop(tfj0CRpnOVyZr+jzc%NV_Xe2ZYz zo80$C7p8{4eR9XhYQfBs&f#}*s4 zRCnQ>iC4bz@1NgyD?-zoL^l`tt!wlIzVe&BexNWw`j?zIHYjLk-7p8#><*~|3cOXc zUqeqe1uxP!1SMN^bByws7ireQdlo%DBzWtnev#*(Hot(k=-;?V0=7p~98n96^5 z_oQMF9(%xrda61v!#%=T#f~&4fhEhAIeayw{>A0!NbX(kd^J>iB=I(gRFRFxMXcQG zRX)QJd?8?;mio0150V)#*)6J*K7Nc>!s+6(@z4Me&DF(gFyx@jnos4odDGFjZSHjk zX-JwsqsO1d=M66EuM|SM@A8|fltQ~VsPVVTL#|AIDu;8YqiyjCWfKMy6%g*^1`|z~ zWF?>-G_SnB+uq&|qEVmTJ69vLyv93o(VTHx@6OA?%};cK)~IplElObbGuqnEzWKM4 z{nZf&C#Q>rP8VR3l%BH$t|OdH&<7DZAIB*FWjRH*7cc^<;4)QJRcQ%+8PtZskI@l; zp}=wGN`Nu}q<7FZIpesaF5Pc-MrZ<(Bg#28>tfOL&86b!u>rmv4cmoO#+-MsKJ1Jb zf72iC%v)#-)}~KZNaU1UxO(;K3s)SV8zjJfIFR9!<)0hH9X=hD*Qg`G->5gV zJn|>CA;@^-BZK+cgo{fcqlC=K1APTCJU1G*FenbiEJApoKyYsobLcwICZaFiI0=(H zn`SJd9cURinKw(vd^X!)vqYEhd;U_Yxbh`5V@*8s*aQh0~b!rVA?;kKK!`nlwwG7D@`e&SJWAbA;%hEq_=J*Oh89}ixdaA!o3Z04Kd1T z@*yDdZ59TY2#)S#WMq`;?G~XvCeH&B(@w;2!j0NJ!|~od{Mnl<4?b{lfK6}~trr)E zE==;p3Lb}nD6s`yEgO9v3xt(dmyTQN64yYqOX=d1@9v1&)<2tl!gzeBtERV%%c6)% zDeepDV^MM-YB#PCISi6F85k|L2ZY=}9=oXisK|ZT+O>SS9&F9Fqm#vyY=72hS`L;q zCU(?NXmZoov)I^BW6lVe@LUEC>iX#|@C|_Q;Pyj!KK4DsNx`xTAy-<{VZqyOe6WSG zyQJU&ZX=CJe9`ZmrL!4f3eXnn0Bj0P-VD$h3DkUr2qfSYCw%X@mrpf)r!&kl$0!ur zx~I8=Pb{O2szS;*I+_y`ZJ;505H@-;frc?AV+_CmMA`>ONlhsB}&ih@8kWKH1fm&A> zT0GwzWJvP-Y@en6L}9f_HoI#wsYH=mzkYevw$BktdpjUQq9yg*h>t3X&Lt#cjfIvh z!%Z9Y+<=CpL4DI=tPyC}Boi8%{AOk%b^reT2wjh+EPruwihRhr66zGC6605{5De%Q zOUIAW2AGag?DOmil$HgY*TGmx85;{xh4^0{6x|Ot9~l$%n0ctB=G;8w54{M8l_g@* z){>RlJLigNx7aiemE%t<0_Yp~>_AHN@JfOMp;s`f?eROkAx-TamIyfgg-L=>2danK zmLdOn>v2e>-k)~x#==v#oKjY;sv^q^iR3A3yAv)$wiIO8&#Ky>GLtU9#RbmQ9ZWd^ zeY3#egILrX#7BB^CYqzuMed~*8}<0|mD{^{aYmeHj~bQdTh+z0D0b^Ii;i|Ve0WX3 zr*d+(kBweY_(L(E6-Bw=drMG#A6Ai{axeHyN|r9at@qb{ z%Hqu8|608Mk8#TXCr$g0l(Z=68bMSa&hVlIyz*Pc`Icclr0s?P04py zXiV~7Ady|T1vO{M1zn$@+9iExZ?fY^uYVSTeS~IL-2!FC9G8|T9(#v^4$a8TRne@R z!4BMM7qO))O6Qx=)o8!&G`t$88z1pmC`>Esr)WvX{gPeHYPYjJU#$KTMo{#ftwaTl#I!XRIq zX9K&vGPy33r0;g0xtnuRo#cG_GD)lf&5_ zMb$KI@g^OU{_0)-8Ew0*cbXHa8|8H&NI@Ijx>fKn-MMV~`o03?u`}RKjf=1wFtcLE zhIFqJ!3wV9A8OLR39|Wq8HB!J4O?QYN4*p;>546;2^>6S8WX8&EyY?yWICpF;FPYX zVAQ_8J)TJ*zh6>&GYS&C`Y(sXN!tpMNfIW{$xP58XBdht`)bmjRKXEOSsHy!+BG~$AzOf*|+svz@|jS0{le(gI| zl`P3RUpI>X2F*UNt1W#dL2aiL_Jw#GU43r!cAEcJs={Z-38as-s=a=8VHsnMcal2`SSdTjIBqnnX`Y-;?urt<{wc)X^jFRN)(xguyR>N88y z*^!GcJNyM4<~NtWF6?_48=dvdy72Q{G2fTWFned7FKaQ;RSWGvl?I@&Aj69ezS!9%fXtGruN#FMD$8TAm-`SL))&{50rtR_) zf@e#fstzCpap3aJDVcS_F=2ds%V%k(+elV#C`Ekg3zTi!>rd>cEZJ9f=gd zLrv9hz&UDy2Vf}#c-e>L_iIt_GO|si1>4EMd%2zn6l)=(EVZ%xowi!{1-#= z8K2=_^4hDr|G%h7r>)%G-Q^@q5&}I~b*GyYc#fqRr+lk&{06UEG2bi4YqD-hNbK*= zZQLnahDv4m)TWsR4Fu4G zKwgW9)@&>I?J$slruwT@j~oG<#7oXZw3XKd_OKJqoEZNxS_uNO2>{h$?#Jbay&$Qh zzbZ^)`om81?2BE6UA0krC60c&3Dh8cTaYBRxe25*K!AD$<&2u(fGA-UcXTQZeAyL! z@r+-(fD&UIdxSdQAAuWs<_PE*EdV(bI)j*08QiB(Zv2%wkpmbE=HoqQhwx~)ANQNA z9DXj6eXNROdtAVD4Tx?*oa@{YDI|1{FYPs8I{heF!QwI&#ah>EA78Erat0;@pv%u7 zLj^-{$b>HRN^n@S(ub~n0Ldz*1Rs!f1YVUuLXy=E@IfLupCfxljBNs6Y2lyLWfKe7 zo;Lve?4XJJ1C0ThDi<&qWexyX$&1NOy7v@h3n__|g?W!Uy94 z(m0$tUtQLh@ie6a(8CwH!P(Ak1V}+JNonVN7<&^o_Z9Li(FVj@F@RD5DOi>k7Ectd zEC`ucEOw@Bah2t>%m)}5sL$l5=Jkbi#q7IF8i#rB2WLCr@HeiPVUisnlk3C7oxu_i zEYR`*kDz$*D7c4McP5qlXZj2fIDjOB{VcaU1$SRAb@8-hC1^tq=p8?iYb-Noq6fZA zsQB_OLU*?&kolUaz^XnH*83z(apt&GtaT`oT}RbM2R#LE%%oCBmQ2M zAI4r1 z_m;6r%)U}8)TL+mrUa_*y$NPhmZ1xKHu(-Omw5N#PM$B050Sif#jM+j;#B=zzJzi9=9`A^2qOIAKpaSSQu&4Qejo6TXxs-)$GRP7N%B$9cn4GAf$nr4(`nU&i22kqm7ga+&{lnF-J&#>?=x z8RU`$ZwYa-`!$$kNmzI01Zs&tI}5Xo{US1&RM47f1_N;NX&1+5vEGm(e6N1MLYujD z(Lk&eNLoSFjz&TQK8!qX%T!8GyLznR=1DZKuRJ)yid!N=KA(mi4JW#2aa+*L-i>6& z?+Qi4)*m| zZ}SHOS%&<<1FR^cCML-HirA`#e=lbRum*$Q2WkH%D#5=YuJKPs=>O3-{x9eo|D^3d z&zjFUD`=$uaafnO-&lIGL0L*9O_QFy#ddbcB+Yw~&>_NTy=UNCq#ySAUOC(hl1;b2 zW$^U|;1`_!ZpIm=`-!qc#ceebo*{mO9^Qdh)uY@sZ5%7&uo1E# zXN8BUf3P${Gw^p+VseAA|DUP^RGZZZfgsa0HNv%fThS<*^W-__o~H2r-AK2T30c6=(Kkt$i(TGftTEJB;(`tQ zlxWoB#r7`lFYSU`atN7!SC!zJS-DXbiu7QbGw-k7J|ff2eMt2tQv8QYr(yI_-CwQBx>KLlJ$_gudkl<3sBeo&mskyuOkiB@V`D1Oyg#mLf zpxXlb?90O?Nbc1!c{vk*CaY{<_>hY+hN@NZ(>dmGygO;Kfvyyp;J{16z41=G!6}hz z8VyI89{?%naQE(+QT6*FrUdhQvvJvVALxz3-u<00Z<))pR#!%)%;A3BU2uepg%vch zmpyEx_=hG@4Jkzsl6^WG(IyUy9A*_- zGY>r>Z1H@tkh^na5gaRBfuv$B$#MKT}!P~#t#8SF3Zv%dZ<>;gj5Z-ODyBx~lCeM|SWO+k*S zl!ePQR9waLiXy`zh)gXllrH_{-U+0aAo*K#WgXM$XWyaw7w4a=o3>Co(;)gVPgLdxZbAZ+Fd0b>Q}-PbgK9eb!99IFE4EY9^okE< zpUDHS${Y8R8%z)qGLH;pas~7y{$90)ug~YK{GgC?OnAT9^;+e?w!KP}&(EMf{~7$8 zp}&!#T_OG5tXEI`?3^jxm%&|EO3p4r+=#12V<@RKK*ebo749 z_QJtiBC_-2dH79wO&}b-G4NqH!QbWGud9Hpv0?WqA1-s4m@sxn%K9h}KB~iI{K2kx zmj4<)b^xk%fQuOFpLq?cXMl!}gWYyM2LqD?@g~qWHdKd6dk;L+&%Rjn+BpwYCXsc0 zD-47X)UfR;q2f(VU}K&J!cvNVK9Dm&F^>%ZdpY7vm;xF=y-G)2f|mdEK#jBXHm}ah zxcN#YkQ>06BQl5xoPEpluy6BWYyin34fGXI!NGsBTqMW3thgeg^V{?qX9iF}2G2e} z6$2`b@MRRq%GMWW^&@1p5Iv;;#}QR3eU)DiqBdh~<^T{#ENDv_tv*i^@)b%JMpoU! z?n_k(FJKd1KcE_bU1dQjlI9f1!Y2`st<;+=U-+lE5m*rD6RpD0RX*)FNc6aFsW%WfPetmOihiAlK{v-fUFyE z>Us9g3t(d06kZCZFAyRE0of4c<@>LRCzyCOA%`A~`BV8uhK7ctReX%3aK-QRN-+Rz zzNJ?Tm+uehWFh)A`i&E?R_gBhSXF+uMny_Ru!NErWdFrCuAue;NM#1}7hlWr z7-=;mi-1rG#r9M+& zN|b?n16e1J&Yb}nQU-`&ZIJTzJ-WiH6>8L4w_K>_5kJ}J@02h~DDs;4lI?eTgH4M0oXv!LvY*gbgC$G?Qv)XC_l^(nfC-vgP~b_(w+9Q}YnZZMG;CE0fDAu{K3&NYPZmt4s4w&$@{4YjFx&{Epx6oOw-A*8kdA?Z2 z&lCqv4R|veV3It+GmQ2ddjTjO?w}H?2zWPG$_eCzJKE9Nxr{j!i<%0BkAosR5atJc z;1|Fzd{!wl2kQp}um!+>WqqkcG6*gYB@WGr{mn*!0 z$oRR`O|H*WUx8x>_(MLo2wb}C_cSA*_?T8>N`LDHkDq}mYnEaT{z+B> z&Z>hzU@=fU1|K!#`++}_jLyN~P(TnS%ZmeP*S{y9$7M_D<$_N#I0$n(Woek#!%fFuF=I0f!Qqb^O_$(MFQds>P5<(3EjzEt?Gmw?p zvoJ+h5M0{Qb5ABWNW3w@cX$jAcw-T0?fr}G0K?1_PyoF?fl+5B885zVIhv_$#+Bd1 zFIoaH1{#k`6K9NrRT4mCr-Kk=FWXoBQeZXY?Nj;^Y#xKOqDiB{F5nFp`Z}<41)pql z!3OjOv@oxiFCuV_0bCnuak_m$B7f!rCil12VVMrO7}(T(eJM2xS#hH<>QCtv|N zz|CIBhUeGb1%FcpC+1t>K>Ru#eL2Z>bjXgHi% z%Xdyu5di}`wRo28zw&pDzHGW5l3j!Jmwz5?bEeqMIBh!O)jKops`03u;|siG;Or?q zMc_rpC>_>H-8cZY&*<6SWtsVj3$Zw6EZ8aW1FsHy2ZxND9MouP^pUP>kqsDHZMajS zvGo>2Xo$Q&pnlAS%_jQLB#G5pAqwRnB424wjHpcxp!@vdawGprxsk6U$LFhO zYo)Gk`T0dSPl%sEi&ab1~FA=hu`-y_*((oyVXUcqsVl1n2 z-gG(8pN=tRycGWu+2D6hnA8W+8#`gcCYDD13LDcaMyXLXhZd_eF|sGVZl2zKEF?FD zUzp_jw)=wFTINYj?0{4Y&yj2ZlhkivD&%JjXEq8YvcJjF*t@BTtJ zed`Zj`#PWxHqX||J$G6u2N9f4!j$VCE7cd#9JUTRTKai=d|;+6BFH62xQWGU`-vN^ zhb>mvMaRLmuySPW<6uFJM8Ik>f`} zE>pwIrAI6I!th^O#FtSrg|_H4ev>Z8&&1*h;R~dS1I2I2E|T2i(0*CI=8afmnV@N4 zqZi+4~578H;h3-u96yE86 zf|Oli9V&ib1_oQCO=bZwi#BC1T|L24E6Wh&08jC~m+UfdC#CM|bAc5ch6ti&yzO?w z7h`TE2<}s?>`8$3(ne!)#Gp)4dZca7WJOBV_hAJ9rh@X(n?`Hj7}Pfa)N)sdIQ0i3(1T)ir-0mThCk$CgZI&?KqYcW`3Z2w5 L*3JIu(l7r9?2iHV literal 35609 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%0DG&`ZA*iM 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 diff --git a/docs/preview/bands-41-3.png b/docs/preview/bands-41-3.png new file mode 100644 index 0000000000000000000000000000000000000000..eddda1f5061d12b6f9cd1b3d8011a2914b9a32e2 GIT binary patch literal 16951 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 CvJw9P literal 0 HcmV?d00001 diff --git a/docs/preview/bands-41-3.svg b/docs/preview/bands-41-3.svg new file mode 100644 index 0000000..7672476 --- /dev/null +++ b/docs/preview/bands-41-3.svg @@ -0,0 +1,21971 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index b8fa464..b9c08d3 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -18,21 +18,61 @@ hattorio-show-outline=Show cell outlines hattorio-band-style-liquid=Dark liquid (animated) hattorio-band-style-void=Void (flat black) hattorio-band-style-rift=Glowing rift (needs Space Age) +hattorio-hat-size-nauvis-15=15 tiles (very hard) +hattorio-hat-size-nauvis-26=26 tiles (hard) +hattorio-hat-size-nauvis-41=41 tiles (normal) +hattorio-hat-size-nauvis-52=52 tiles (easy) +hattorio-band-width-nauvis-1=1 tile (thin) +hattorio-band-width-nauvis-2=2 tiles (normal) +hattorio-band-width-nauvis-3=3 tiles (wide) +hattorio-band-width-nauvis-4=4 tiles (very wide) +hattorio-hat-size-vulcanus-15=15 tiles (very hard) +hattorio-hat-size-vulcanus-26=26 tiles (hard) +hattorio-hat-size-vulcanus-41=41 tiles (normal) +hattorio-hat-size-vulcanus-52=52 tiles (easy) +hattorio-band-width-vulcanus-1=1 tile (thin) +hattorio-band-width-vulcanus-2=2 tiles (normal) +hattorio-band-width-vulcanus-3=3 tiles (wide) +hattorio-band-width-vulcanus-4=4 tiles (very wide) +hattorio-hat-size-fulgora-15=15 tiles (very hard) +hattorio-hat-size-fulgora-26=26 tiles (hard) +hattorio-hat-size-fulgora-41=41 tiles (normal) +hattorio-hat-size-fulgora-52=52 tiles (easy) +hattorio-band-width-fulgora-1=1 tile (thin) +hattorio-band-width-fulgora-2=2 tiles (normal) +hattorio-band-width-fulgora-3=3 tiles (wide) +hattorio-band-width-fulgora-4=4 tiles (very wide) +hattorio-hat-size-gleba-15=15 tiles (very hard) +hattorio-hat-size-gleba-26=26 tiles (hard) +hattorio-hat-size-gleba-41=41 tiles (normal) +hattorio-hat-size-gleba-52=52 tiles (easy) +hattorio-band-width-gleba-1=1 tile (thin) +hattorio-band-width-gleba-2=2 tiles (normal) +hattorio-band-width-gleba-3=3 tiles (wide) +hattorio-band-width-gleba-4=4 tiles (very wide) +hattorio-hat-size-aquilo-15=15 tiles (very hard) +hattorio-hat-size-aquilo-26=26 tiles (hard) +hattorio-hat-size-aquilo-41=41 tiles (normal) +hattorio-hat-size-aquilo-52=52 tiles (easy) +hattorio-band-width-aquilo-1=1 tile (thin) +hattorio-band-width-aquilo-2=2 tiles (normal) +hattorio-band-width-aquilo-3=3 tiles (wide) +hattorio-band-width-aquilo-4=4 tiles (very wide) [mod-setting-description] hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes. Void is a flat, still hole. Glowing rift uses the lava shader and needs Space Age, falling back to dark liquid without it. hattorio-richness-override=Multiplier applied to ore in newly generated chunks, replacing the automatic compensation for ore the bands put out of reach. Leave at 0 to compensate automatically from your cell size and band width. hattorio-show-outline=Draw a line along the centre of each band. Affects only your own view. -hattorio-hat-size-nauvis=Distance from a cell's centre to its furthest corner, in tiles. Larger cells give more room and let larger blueprints be reused. -hattorio-band-width-nauvis=Width of the unbuildable band between cells, in tiles. Keep below underground belt reach or cells become isolated. -hattorio-hat-size-vulcanus=Distance from a cell's centre to its furthest corner, in tiles. -hattorio-band-width-vulcanus=Width of the unbuildable band between cells, in tiles. -hattorio-hat-size-fulgora=Distance from a cell's centre to its furthest corner, in tiles. -hattorio-band-width-fulgora=Width of the unbuildable band between cells, in tiles. -hattorio-hat-size-gleba=Distance from a cell's centre to its furthest corner, in tiles. -hattorio-band-width-gleba=Width of the unbuildable band between cells, in tiles. -hattorio-hat-size-aquilo=Distance from a cell's centre to its furthest corner, in tiles. -hattorio-band-width-aquilo=Width of the unbuildable band between cells, in tiles. +hattorio-hat-size-nauvis=How large each buildable cell is. Smaller cells are harder: less room, and only smaller blueprints stay reusable. 15 gives about 116 tiles per cell, 41 about 944, 52 about 1,584. +hattorio-band-width-nauvis=How wide the unbuildable band between cells is. Wider bands take more of the map and make crossings more awkward. All widths can be spanned by underground belts. +hattorio-hat-size-vulcanus=How large each buildable cell is. Smaller cells are harder. +hattorio-band-width-vulcanus=How wide the unbuildable band between cells is. +hattorio-hat-size-fulgora=How large each buildable cell is. Smaller cells are harder. +hattorio-band-width-fulgora=How wide the unbuildable band between cells is. +hattorio-hat-size-gleba=How large each buildable cell is. Smaller cells are harder. +hattorio-band-width-gleba=How wide the unbuildable band between cells is. +hattorio-hat-size-aquilo=How large each buildable cell is. Smaller cells are harder. +hattorio-band-width-aquilo=How wide the unbuildable band between cells is. [tile-name] hattorio-band=Cell boundary diff --git a/mod/config.lua b/mod/config.lua index 052e8a0..0fdb349 100644 --- a/mod/config.lua +++ b/mod/config.lua @@ -9,7 +9,19 @@ local Config = {} Config.MIN_SIZE, Config.MAX_SIZE = 15, 90 Config.MIN_BAND, Config.MAX_BAND = 1, 6 -Config.DEFAULT_SIZE, Config.DEFAULT_BAND = 26, 2 +Config.DEFAULT_SIZE, Config.DEFAULT_BAND = 41, 3 + +-- The sizes and widths offered in the settings dropdowns. MIN/MAX above still +-- bound anything that arrives from elsewhere; these are only the choices a +-- player is given, each measured rather than guessed. +-- +-- size cell area largest blueprint reusable in EVERY cell +-- 15 116 t ~5x5 very hard +-- 26 347 t 11x11 hard +-- 41 944 t 18x18 normal +-- 52 1,584 t 23x23 easy +Config.SIZE_CHOICES = { 15, 26, 41, 52 } +Config.BAND_CHOICES = { 1, 2, 3, 4 } -- A Factorio map is 2e6 x 2e6, so its half-diagonal is ~1.41e6. Ti:coverage() -- is the CIRCUMRADIUS of a hexagonal root, and a square region inscribed in diff --git a/mod/surface.lua b/mod/surface.lua index 0c0dce9..0b61012 100644 --- a/mod/surface.lua +++ b/mod/surface.lua @@ -23,10 +23,12 @@ function Surface.is_tiled(surface) return planet_of(surface) ~= nil end -local function read_setting(name, default) +-- The size and band dropdowns hold strings, as Factorio requires for +-- allowed_values. Convert here, at the single point where settings are read. +local function read_number_setting(name, default) local s = settings.startup[name] if s == nil then return default end - return s.value + return tonumber(s.value) or default end --- Derive and store this surface's geometry. Idempotent. @@ -39,8 +41,8 @@ function Surface.init(surface) if existing then return existing end local g = Config.geometry( - read_setting("hattorio-hat-size-" .. planet, Config.DEFAULT_SIZE), - read_setting("hattorio-band-width-" .. planet, Config.DEFAULT_BAND)) + read_number_setting("hattorio-hat-size-" .. planet, Config.DEFAULT_SIZE), + read_number_setting("hattorio-band-width-" .. planet, Config.DEFAULT_BAND)) g.planet = planet g.tile = "hattorio-band-" .. planet g.mirror_tile = "hattorio-band-" .. planet .. "-mirror" diff --git a/prototypes/settings.lua b/prototypes/settings.lua index d74c013..7db9f8c 100644 --- a/prototypes/settings.lua +++ b/prototypes/settings.lua @@ -13,22 +13,24 @@ local order = 0 for planet, _ in pairs(planets) do order = order + 1 + -- Dropdowns rather than sliders: the difficulty of a given size is not + -- obvious from the number, and the interesting range is four values, not + -- seventy-six. Values are strings, as Factorio requires for a dropdown, and + -- mod/surface.lua converts them. settings_list[#settings_list + 1] = { - type = "int-setting", + type = "string-setting", name = "hattorio-hat-size-" .. planet, setting_type = "startup", - default_value = 26, - minimum_value = 15, - maximum_value = 90, + default_value = "41", + allowed_values = { "15", "26", "41", "52" }, order = string.format("a[size]-%02d[%s]", order, planet), } settings_list[#settings_list + 1] = { - type = "int-setting", + type = "string-setting", name = "hattorio-band-width-" .. planet, setting_type = "startup", - default_value = 2, - minimum_value = 1, - maximum_value = 6, + default_value = "3", + allowed_values = { "1", "2", "3", "4" }, order = string.format("b[band]-%02d[%s]", order, planet), } end diff --git a/spec/config_spec.lua b/spec/config_spec.lua index eca91f8..5b4fece 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -75,20 +75,11 @@ describe("config", function() assert.is_true(Config.richness_multiplier(26, 4) > Config.richness_multiplier(26, 1)) end) - it("defaults to 26 and 2", function() - assert.are.equal(26, Config.DEFAULT_SIZE) - assert.are.equal(2, Config.DEFAULT_BAND) + it("defaults to 41 and 3", function() + assert.are.equal(41, Config.DEFAULT_SIZE) + assert.are.equal(3, Config.DEFAULT_BAND) end) - it("matches the bounds declared in prototypes/settings.lua", function() - local f = assert(io.open("prototypes/settings.lua", "r")) - local src = f:read("*a"); f:close() - assert.is_not_nil(src:find("default_value = " .. Config.DEFAULT_SIZE)) - assert.is_not_nil(src:find("minimum_value = " .. Config.MIN_SIZE)) - assert.is_not_nil(src:find("maximum_value = " .. Config.MAX_SIZE)) - assert.is_not_nil(src:find("default_value = " .. Config.DEFAULT_BAND)) - assert.is_not_nil(src:find("maximum_value = " .. Config.MAX_BAND)) - end) end) describe("richness calibration", function() @@ -120,4 +111,39 @@ describe("richness calibration", function() string.format("size %d band %d: effective yield %.2f", size, band, effective)) end end) + + it("offers only choices it can actually satisfy", function() + for _, size in ipairs(Config.SIZE_CHOICES) do + for _, band in ipairs(Config.BAND_CHOICES) do + local g = Config.geometry(size, band) + assert.are.equal(size, g.size) + assert.are.equal(band, g.band) + end + end + end) + + it("declares the same dropdown values as prototypes/settings.lua", function() + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + assert.is_not_nil(src:find('default_value = "' .. Config.DEFAULT_SIZE .. '"', 1, true), + "settings.lua default size differs from Config.DEFAULT_SIZE") + assert.is_not_nil(src:find('default_value = "' .. Config.DEFAULT_BAND .. '"', 1, true), + "settings.lua default band differs from Config.DEFAULT_BAND") + for _, v in ipairs(Config.SIZE_CHOICES) do + assert.is_not_nil(src:find('"' .. v .. '"', 1, true), "size choice " .. v .. " missing") + end + end) + + it("has a locale label for every dropdown value", function() + local f = assert(io.open("locale/en/hattorio.cfg", "r")) + local src = f:read("*a"); f:close() + for _, v in ipairs(Config.SIZE_CHOICES) do + assert.is_not_nil(src:find("hattorio%-hat%-size%-nauvis%-" .. v .. "="), + "no label for size " .. v) + end + for _, v in ipairs(Config.BAND_CHOICES) do + assert.is_not_nil(src:find("hattorio%-band%-width%-nauvis%-" .. v .. "="), + "no label for band " .. v) + end + end) end) diff --git a/wiki/Settings.md b/wiki/Settings.md index d7baecd..7ccd353 100644 --- a/wiki/Settings.md +++ b/wiki/Settings.md @@ -2,31 +2,36 @@ Two settings per planet, both fixed when the world is created. -| Setting | Default | Range | +| Setting | Default | Choices | |---|---|---| -| Hat size (centre to vertex, in tiles) | **26** | 15–90 | -| Band width (tiles) | **2** | 1–6 | +| Cell size (centre to vertex, in tiles) | **41** (normal) | 15, 26, 41, 52 | +| Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | +| Band appearance | Dark liquid | liquid, void, glowing rift | + +Cell size and band width are dropdowns, labelled with how hard each makes the +game. They are set per planet. ## Hat size is the dial It decides how much room a cell gives you, and — more importantly — how large a blueprint can still be reused everywhere. -| Size | Cell area | Unbuildable | Largest blueprint that fits every cell | +| Size | Difficulty | Cell area | Largest blueprint that fits every cell | |---|---|---|---| -| 15 | 116 tiles | 37% | about 5×5 | -| **26** (default) | **347 tiles** | **22%** | **11×11** | -| 41 | 944 tiles | 15% | 18×18 | -| 52 | 1,584 tiles | 12% | 23×23 | +| 15 | very hard | 116 tiles | about 5×5 | +| 26 | hard | 347 tiles | 11×11 | +| **41** (default) | **normal** | **944 tiles** | **18×18** | +| 52 | easy | 1,584 tiles | 23×23 | For scale: a smelter block is around 20×20 and a mall around 30×30. **Every size on this table breaks those**, so the choice is not whether blueprints survive — it is how much room you get to improvise in. - **15** is brutal. One machine and some belt. Novel, probably exhausting. -- **26** is the default because 11×11 is roughly one assembler cluster: enough - that you are building, not fighting, while everything larger is hand-fitted. -- **41** is comfortable. Real sub-builds per cell, spaghetti mostly between them. +- **26** is hard. 11×11 is roughly one assembler cluster: enough that you are + building, not fighting, while everything larger is hand-fitted. +- **41** is the default. Real sub-builds per cell, spaghetti mostly between + them, and every imported blueprint still breaks. - **52** matches Hextorio's hexagons by area, if you want that feel. ![size 15](https://raw.githubusercontent.com/vetkat/hattorio/main/docs/preview/bands-15-2.png) @@ -34,6 +39,13 @@ survive — it is how much room you get to improvise in. ![size 41](https://raw.githubusercontent.com/vetkat/hattorio/main/docs/preview/bands-41-2.png) ![size 52](https://raw.githubusercontent.com/vetkat/hattorio/main/docs/preview/bands-52-2.png) +## Band appearance + +Three styles, all of them dark. **Dark liquid** is the default: an animated +surface that moves and oozes. **Void** is flat and still, a hole rather than a +liquid. **Glowing rift** uses the lava shader and needs Space Age; without it +the mod falls back to dark liquid and says so in the log. + ## Band width How wide the unbuildable strip is. Keep it below underground-belt reach or From 08b17d6edb2324e763540a34c39b6425e38c7058 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 14:41:50 +0200 Subject: [PATCH 10/17] feat(mod): band colour as its own dropdown, deep violet by default Colour is independent of style: the style picks the shader, the colour picks what it is tinted, so a glowing rift can be violet and a flat void can be ember. Colour is no longer per planet. It is one player-chosen look shared across every surface, so the mod reads as one thing rather than five. Reflected cells keep a stronger tint of the same colourway. Tests assert the colourways and the settings dropdown agree in both directions, that every one has a locale label, and that every body stays near-black with a lighter highlight -- without which an option is invisible. --- README.md | 1 + docs/design/hattorio-design.md | 7 +- docs/preview/bands-41-3.png | Bin 16951 -> 16952 bytes docs/preview/bands-41-3.svg | 43938 +++++++++++++++---------------- locale/en/hattorio.cfg | 7 + mod/colours.lua | 19 + mod/planets.lua | 27 +- prototypes/settings.lua | 15 +- prototypes/tiles.lua | 43 +- spec/config_spec.lua | 57 + wiki/Settings.md | 21 +- 11 files changed, 22125 insertions(+), 22010 deletions(-) create mode 100644 mod/colours.lua diff --git a/README.md b/README.md index 671b92b..6ece0bf 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Per planet, set at world creation. | Cell size (centre to vertex, tiles) | **41** (normal) | 15, 26, 41, 52 | | Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | | Band appearance | Dark liquid | liquid, void, glowing rift | +| Band colour | Deep violet | violet, cold, oily, ember, void black | Hat size is the dial that matters. It decides how much room you get and how large a blueprint survives: diff --git a/docs/design/hattorio-design.md b/docs/design/hattorio-design.md index 7ce973f..5e8bd02 100644 --- a/docs/design/hattorio-design.md +++ b/docs/design/hattorio-design.md @@ -412,6 +412,7 @@ Per planet, startup: | `hattorio-hat-size-` | **41** (normal) | 15, 26, 41, 52 | | `hattorio-band-width-` | **3** (wide) | 1, 2, 3, 4 | | `hattorio-band-style` | liquid | liquid, void, rift | +| `hattorio-band-colour` | violet | violet, cold, oily, ember, ink | | `hattorio-richness-override` (map) | 0 = automatic | 0 - 10 | | `hattorio-show-outline` (per player) | off | on, off | @@ -426,7 +427,11 @@ cramped in practice. 41 gives a 944-tile cell where blueprints up to still breaks. Band 3 keeps the bands visually substantial, which band 2 does not at that cell size. -Only size and band are per planet. Band style is global, and the other +Band style and band colour are **independent**: the style picks the +shader laid over the surface, the colour picks what it is tinted, so a +glowing rift can be violet and a flat void can be ember. + +Only size and band are per planet. Band style and colour are global, and the other two are scoped by what they affect: richness is map-wide because ore is shared, and the outline is per player because it is pure presentation and cannot change what anyone else sees. diff --git a/docs/preview/bands-41-3.png b/docs/preview/bands-41-3.png index eddda1f5061d12b6f9cd1b3d8011a2914b9a32e2..8698c88f916294156f88902718ce4bfe5e9f09dc 100644 GIT binary patch literal 16952 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 CvJw9P diff --git a/docs/preview/bands-41-3.svg b/docs/preview/bands-41-3.svg index 7672476..400d66e 100644 --- a/docs/preview/bands-41-3.svg +++ b/docs/preview/bands-41-3.svg @@ -1,21971 +1,21971 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index b9c08d3..036bfb6 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -11,6 +11,7 @@ hattorio-hat-size-aquilo=Cell size (Aquilo) hattorio-band-width-aquilo=Band width (Aquilo) hattorio-band-style=Band appearance +hattorio-band-colour=Band colour hattorio-richness-override=Ore richness override hattorio-show-outline=Show cell outlines @@ -18,6 +19,11 @@ hattorio-show-outline=Show cell outlines hattorio-band-style-liquid=Dark liquid (animated) hattorio-band-style-void=Void (flat black) hattorio-band-style-rift=Glowing rift (needs Space Age) +hattorio-band-colour-violet=Deep violet +hattorio-band-colour-cold=Near-black, cold +hattorio-band-colour-oily=Oily green-black +hattorio-band-colour-ember=Ember black +hattorio-band-colour-ink=Pure void black hattorio-hat-size-nauvis-15=15 tiles (very hard) hattorio-hat-size-nauvis-26=26 tiles (hard) hattorio-hat-size-nauvis-41=41 tiles (normal) @@ -61,6 +67,7 @@ hattorio-band-width-aquilo-4=4 tiles (very wide) [mod-setting-description] hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes. Void is a flat, still hole. Glowing rift uses the lava shader and needs Space Age, falling back to dark liquid without it. +hattorio-band-colour=What colour the bands are tinted. Every option is near-black; they differ in the highlight the surface catches at its edges. Reflected cells, about one in eight, are tinted more strongly so you can pick them out. hattorio-richness-override=Multiplier applied to ore in newly generated chunks, replacing the automatic compensation for ore the bands put out of reach. Leave at 0 to compensate automatically from your cell size and band width. hattorio-show-outline=Draw a line along the centre of each band. Affects only your own view. hattorio-hat-size-nauvis=How large each buildable cell is. Smaller cells are harder: less room, and only smaller blueprints stay reusable. 15 gives about 116 tiles per cell, 41 about 944, 52 about 1,584. diff --git a/mod/colours.lua b/mod/colours.lua new file mode 100644 index 0000000..5a848e8 --- /dev/null +++ b/mod/colours.lua @@ -0,0 +1,19 @@ +-- Band colourways, selected by a startup setting. +-- +-- `body` is the surface itself and is near-black in every option: the bands +-- are meant to read as depth, not as paint. `highlight` is what the shader +-- catches at the edges, and is what actually distinguishes one option from +-- another -- comparing the bodies alone, they are indistinguishable. +-- +-- `mirror` tints the reflected cells, about one in eight, which are a +-- mathematical property of this tiling rather than a decoration. +-- +-- Colour is independent of band style: any of these works with the animated +-- liquid, the flat void or the glowing rift. +return { + violet = { body = { 30, 19, 48 }, highlight = { 74, 45, 114 }, mirror = { 150, 96, 200 } }, + cold = { body = { 23, 27, 34 }, highlight = { 46, 58, 69 }, mirror = { 70, 110, 150 } }, + oily = { body = { 14, 26, 19 }, highlight = { 31, 63, 43 }, mirror = { 90, 170, 110 } }, + ember = { body = { 29, 16, 11 }, highlight = { 90, 42, 18 }, mirror = { 210, 110, 50 } }, + ink = { body = { 4, 4, 6 }, highlight = { 10, 10, 14 }, mirror = { 90, 90, 110 } }, +} diff --git a/mod/planets.lua b/mod/planets.lua index 29c8b36..b4d06e4 100644 --- a/mod/planets.lua +++ b/mod/planets.lua @@ -1,20 +1,15 @@ --- Which surfaces get tiled, and what the band looks like there. +-- Which surfaces get tiled. -- --- All three band styles clone `deepwater`. That is not laziness: its --- transitions are authored for "liquid meets land", which is exactly the edge --- a rift needs, and it is the only base tile carrying an animated shader. --- The styles differ only in the effect and the colours laid over it. +-- A set rather than a list: mod/surface.lua tests membership by name, and +-- anything absent here is left as vanilla terrain -- including space +-- platforms and any planet a mod adds that we have no settings for. -- --- liquid the water shader, recoloured dark -- it moves and oozes --- void no effect at all -- a flat, still hole in the world --- rift Vulcanus's lava shader -- glows, REQUIRES Space Age --- --- effect_color is the body of the surface; effect_color_secondary is the --- highlight the shader catches at the edges. +-- Band colour is NOT per planet. It is a player setting, shared across every +-- surface, so that the mod looks like one thing rather than five. return { - nauvis = { map_color = { 23, 27, 34 }, mirror_color = { 20, 121, 201 } }, - vulcanus = { map_color = { 29, 16, 11 }, mirror_color = { 176, 64, 32 } }, - fulgora = { map_color = { 30, 19, 48 }, mirror_color = { 150, 96, 200 } }, - gleba = { map_color = { 14, 26, 19 }, mirror_color = { 120, 170, 70 } }, - aquilo = { map_color = { 26, 32, 38 }, mirror_color = { 120, 180, 210 } }, + nauvis = true, + vulcanus = true, + fulgora = true, + gleba = true, + aquilo = true, } diff --git a/prototypes/settings.lua b/prototypes/settings.lua index 7db9f8c..cf34951 100644 --- a/prototypes/settings.lua +++ b/prototypes/settings.lua @@ -46,7 +46,20 @@ settings_list[#settings_list + 1] = { setting_type = "startup", default_value = "liquid", allowed_values = { "liquid", "void", "rift" }, - order = "c[style]", + order = "c[style]-a[style]", +} + +-- Band colour, independent of style: any colourway works with any shader. +-- Deep violet by default -- it is the only option that reads as deliberate +-- rather than as dark ground, since every body colour here is near-black and +-- it is the highlight that distinguishes them. +settings_list[#settings_list + 1] = { + type = "string-setting", + name = "hattorio-band-colour", + setting_type = "startup", + default_value = "violet", + allowed_values = { "violet", "cold", "oily", "ember", "ink" }, + order = "c[style]-b[colour]", } -- Map-scoped, because ore is shared between everyone on it. 0 means "use the diff --git a/prototypes/tiles.lua b/prototypes/tiles.lua index 4201ca3..476d1be 100644 --- a/prototypes/tiles.lua +++ b/prototypes/tiles.lua @@ -1,30 +1,35 @@ local planets = require("mod.planets") +local colours = require("mod.colours") --- Band appearance is a startup setting, so the tiles differ by what the --- player chose. All three styles clone `deepwater`: its transitions are --- authored for liquid meeting land, which is the edge a rift wants, and it --- carries the only animated shader in the base game. +-- Band appearance is two independent startup settings: STYLE picks the shader +-- laid over the surface, COLOUR picks what that shader is tinted. They are +-- orthogonal, so a glowing rift can be violet and a flat void can be ember. +-- +-- All styles clone `deepwater`. Its transitions are authored for liquid +-- meeting land, which is the edge a rift wants, and it carries the only +-- animated shader in the base game. local STYLE = settings.startup["hattorio-band-style"].value +local COLOUR = settings.startup["hattorio-band-colour"].value -- The glowing style needs Vulcanus's lava shader, which only Space Age ships. --- Fall back rather than fail to load, and say so in the log. +-- Fall back rather than refuse to load, and say so in the log. if STYLE == "rift" and not mods["space-age"] then log("hattorio: band style 'rift' needs Space Age for the lava shader; " .. "falling back to 'liquid'") STYLE = "liquid" end --- Body and highlight colours per style. The body is deliberately near-black --- in every case; the highlight is what distinguishes them. -local STYLE_COLOURS = { - liquid = { body = { 23, 27, 34 }, highlight = { 46, 58, 69 }, effect = "water" }, - void = { body = { 8, 9, 12 }, highlight = { 8, 9, 12 }, effect = nil }, - rift = { body = { 26, 12, 8 }, highlight = { 167, 59, 27 }, effect = "lava-2" }, +-- Only the effect differs between styles; the colours come from COLOUR. +local STYLE_EFFECT = { + liquid = "water", + void = nil, + rift = "lava-2", } -local style = STYLE_COLOURS[STYLE] or STYLE_COLOURS.liquid +local c = colours[COLOUR] or colours.violet +local effect = STYLE_EFFECT[STYLE] -local function band_tile(name, map_color) +local function band_tile(name, highlight, map_color) local src = data.raw.tile["deepwater"] if not src then error("hattorio: base tile 'deepwater' does not exist") @@ -39,9 +44,9 @@ local function band_tile(name, map_color) -- vehicles pass over it -- a rift you can step across. t.collision_mask = { layers = { hattorio_band = true } } - t.effect = style.effect - t.effect_color = style.body - t.effect_color_secondary = style.highlight + t.effect = effect + t.effect_color = c.body + t.effect_color_secondary = highlight -- Not water: must not feed an offshore pump, must not merge with lakes, -- must not be minable or appear in blueprints, and must never autoplace. @@ -58,9 +63,9 @@ local function band_tile(name, map_color) end local tiles = {} -for planet, cfg in pairs(planets) do - tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet, cfg.map_color) - tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet .. "-mirror", cfg.mirror_color) +for planet, _ in pairs(planets) do + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet, c.highlight, c.body) + tiles[#tiles + 1] = band_tile("hattorio-band-" .. planet .. "-mirror", c.mirror, c.mirror) end data:extend(tiles) diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 5b4fece..5b445c9 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -147,3 +147,60 @@ describe("richness calibration", function() end end) end) + +describe("band colours", function() + local colours = require("mod.colours") + + it("declares the same colourways as prototypes/settings.lua", function() + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + local allowed = src:match('name = "hattorio%-band%-colour".-allowed_values = {(.-)}') + assert.is_not_nil(allowed, "no band colour setting found") + for name, _ in pairs(colours) do + assert.is_not_nil(allowed:find('"' .. name .. '"', 1, true), + "colourway " .. name .. " is not offered in settings") + end + for quoted in allowed:gmatch('"([%a]+)"') do + assert.is_not_nil(colours[quoted], "settings offer unknown colourway " .. quoted) + end + end) + + it("defaults to deep violet", function() + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + local block = src:match('name = "hattorio%-band%-colour".-order') + assert.is_not_nil(block:find('default_value = "violet"', 1, true)) + end) + + it("gives every colourway a body, a highlight and a mirror tint", function() + for name, c in pairs(colours) do + for _, key in ipairs({ "body", "highlight", "mirror" }) do + assert.is_table(c[key], name .. " is missing " .. key) + assert.are.equal(3, #c[key], name .. "." .. key .. " is not RGB") + for i = 1, 3 do + assert.is_true(c[key][i] >= 0 and c[key][i] <= 255, + name .. "." .. key .. " component out of range") + end + end + end + end) + + it("has a locale label for every colourway", function() + local f = assert(io.open("locale/en/hattorio.cfg", "r")) + local src = f:read("*a"); f:close() + for name, _ in pairs(colours) do + assert.is_not_nil(src:find("hattorio%-band%-colour%-" .. name .. "="), + "no label for colourway " .. name) + end + end) + + it("keeps every body near-black, so bands read as depth", function() + for name, c in pairs(colours) do + local brightness = (c.body[1] + c.body[2] + c.body[3]) / 3 + assert.is_true(brightness < 40, name .. " body is too light: " .. brightness) + -- the highlight must actually be lighter, or the option is invisible + local hl = (c.highlight[1] + c.highlight[2] + c.highlight[3]) / 3 + assert.is_true(hl >= brightness, name .. " highlight is not lighter than its body") + end + end) +end) diff --git a/wiki/Settings.md b/wiki/Settings.md index 7ccd353..928ae2f 100644 --- a/wiki/Settings.md +++ b/wiki/Settings.md @@ -7,6 +7,7 @@ Two settings per planet, both fixed when the world is created. | Cell size (centre to vertex, in tiles) | **41** (normal) | 15, 26, 41, 52 | | Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | | Band appearance | Dark liquid | liquid, void, glowing rift | +| Band colour | Deep violet | violet, cold, oily, ember, void black | Cell size and band width are dropdowns, labelled with how hard each makes the game. They are set per planet. @@ -41,10 +42,22 @@ survive — it is how much room you get to improvise in. ## Band appearance -Three styles, all of them dark. **Dark liquid** is the default: an animated -surface that moves and oozes. **Void** is flat and still, a hole rather than a -liquid. **Glowing rift** uses the lava shader and needs Space Age; without it -the mod falls back to dark liquid and says so in the log. +Two independent settings: what the bands are made of, and what colour they +are. Any combination works. + +**Style** picks the surface. *Dark liquid* is the default: animated, it moves +and oozes. *Void* is flat and still, a hole rather than a liquid. *Glowing +rift* uses the lava shader and needs Space Age; without it the mod falls back +to dark liquid and says so in the log. + +**Colour** tints it. Every option is near-black on purpose, so the bands read +as depth rather than paint — what separates them is the highlight the surface +catches at its edges. *Deep violet* is the default and the most deliberate +looking; *near-black cold*, *oily green-black* and *ember black* are quieter; +*pure void black* is a flat hole with no sheen at all. + +Reflected cells — about one in eight, and mathematically unavoidable in this +tiling — are tinted more strongly so you can pick them out. ## Band width From a15606a02002cf4dfe229a0e09fa7be873cb384e Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 14:54:55 +0200 Subject: [PATCH 11/17] feat(mod): drop the glowing rift style, lift the highlights The lava-shader style read as a hazard rather than a void and fought the calm look of the rest of the mod. Removing it also drops a Space Age conditional from the data stage. Highlights lifted about a third across every colourway, which is what the shader catches at a band's edges and the only thing that tells the options apart. Two tests now encode that: a highlight must be clearly lighter than its body, and no two colourways may have near-identical highlights. --- README.md | 2 +- docs/design/hattorio-design.md | 10 ++++-- docs/preview/band-colours.png | Bin 22221 -> 21706 bytes locale/en/hattorio.cfg | 3 +- mod/colours.lua | 14 ++++----- prototypes/settings.lua | 6 +--- prototypes/tiles.lua | 17 +++------- spec/config_spec.lua | 56 +++++++++++++++++++++++++++++++++ wiki/Settings.md | 6 ++-- 9 files changed, 80 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6ece0bf..c6d1fe8 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Per planet, set at world creation. |---|---|---| | Cell size (centre to vertex, tiles) | **41** (normal) | 15, 26, 41, 52 | | Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | -| Band appearance | Dark liquid | liquid, void, glowing rift | +| Band appearance | Dark liquid | liquid, void | | Band colour | Deep violet | violet, cold, oily, ember, void black | Hat size is the dial that matters. It decides how much room you get and how diff --git a/docs/design/hattorio-design.md b/docs/design/hattorio-design.md index 5e8bd02..ffcb030 100644 --- a/docs/design/hattorio-design.md +++ b/docs/design/hattorio-design.md @@ -411,7 +411,7 @@ Per planet, startup: |---|---|---| | `hattorio-hat-size-` | **41** (normal) | 15, 26, 41, 52 | | `hattorio-band-width-` | **3** (wide) | 1, 2, 3, 4 | -| `hattorio-band-style` | liquid | liquid, void, rift | +| `hattorio-band-style` | liquid | liquid, void | | `hattorio-band-colour` | violet | violet, cold, oily, ember, ink | | `hattorio-richness-override` (map) | 0 = automatic | 0 - 10 | | `hattorio-show-outline` (per player) | off | on, off | @@ -428,8 +428,12 @@ still breaks. Band 3 keeps the bands visually substantial, which band 2 does not at that cell size. Band style and band colour are **independent**: the style picks the -shader laid over the surface, the colour picks what it is tinted, so a -glowing rift can be violet and a flat void can be ember. +shader laid over the surface, the colour picks what it is tinted. + +A third style using Vulcanus's lava shader was built and then removed +after playtesting -- it read as a hazard rather than a void and fought +the calm look of everything else. Removing it also dropped a Space Age +conditional from the data stage. Only size and band are per planet. Band style and colour are global, and the other two are scoped by what they affect: richness is map-wide because ore is diff --git a/docs/preview/band-colours.png b/docs/preview/band-colours.png index 63a17963316db9e919536eb8ce6578598c134dbf..98cff9fd90bf623b43207f669001e5196145f2d8 100644 GIT binary patch literal 21706 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 L*3JIu(l7r9?2iHV diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index 036bfb6..50f8709 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -18,7 +18,6 @@ hattorio-show-outline=Show cell outlines [string-mod-setting] hattorio-band-style-liquid=Dark liquid (animated) hattorio-band-style-void=Void (flat black) -hattorio-band-style-rift=Glowing rift (needs Space Age) hattorio-band-colour-violet=Deep violet hattorio-band-colour-cold=Near-black, cold hattorio-band-colour-oily=Oily green-black @@ -66,7 +65,7 @@ hattorio-band-width-aquilo-3=3 tiles (wide) hattorio-band-width-aquilo-4=4 tiles (very wide) [mod-setting-description] -hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes. Void is a flat, still hole. Glowing rift uses the lava shader and needs Space Age, falling back to dark liquid without it. +hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes; void is a flat, still hole. hattorio-band-colour=What colour the bands are tinted. Every option is near-black; they differ in the highlight the surface catches at its edges. Reflected cells, about one in eight, are tinted more strongly so you can pick them out. hattorio-richness-override=Multiplier applied to ore in newly generated chunks, replacing the automatic compensation for ore the bands put out of reach. Leave at 0 to compensate automatically from your cell size and band width. hattorio-show-outline=Draw a line along the centre of each band. Affects only your own view. diff --git a/mod/colours.lua b/mod/colours.lua index 5a848e8..f6350cf 100644 --- a/mod/colours.lua +++ b/mod/colours.lua @@ -8,12 +8,12 @@ -- `mirror` tints the reflected cells, about one in eight, which are a -- mathematical property of this tiling rather than a decoration. -- --- Colour is independent of band style: any of these works with the animated --- liquid, the flat void or the glowing rift. +-- Colour is independent of band style: each works with the animated liquid +-- and with the flat void. return { - violet = { body = { 30, 19, 48 }, highlight = { 74, 45, 114 }, mirror = { 150, 96, 200 } }, - cold = { body = { 23, 27, 34 }, highlight = { 46, 58, 69 }, mirror = { 70, 110, 150 } }, - oily = { body = { 14, 26, 19 }, highlight = { 31, 63, 43 }, mirror = { 90, 170, 110 } }, - ember = { body = { 29, 16, 11 }, highlight = { 90, 42, 18 }, mirror = { 210, 110, 50 } }, - ink = { body = { 4, 4, 6 }, highlight = { 10, 10, 14 }, mirror = { 90, 90, 110 } }, + violet = { body = { 30, 19, 48 }, highlight = { 98, 60, 152 }, mirror = { 158, 104, 208 } }, + cold = { body = { 23, 27, 34 }, highlight = { 62, 78, 93 }, mirror = { 82, 124, 166 } }, + oily = { body = { 14, 26, 19 }, highlight = { 42, 85, 58 }, mirror = { 102, 180, 122 } }, + ember = { body = { 29, 16, 11 }, highlight = { 120, 56, 24 }, mirror = { 216, 120, 56 } }, + ink = { body = { 4, 4, 6 }, highlight = { 18, 18, 24 }, mirror = { 104, 104, 126 } }, } diff --git a/prototypes/settings.lua b/prototypes/settings.lua index cf34951..6f0eb46 100644 --- a/prototypes/settings.lua +++ b/prototypes/settings.lua @@ -36,16 +36,12 @@ for planet, _ in pairs(planets) do end -- Band appearance. Startup, because it selects which tile prototypes exist. --- --- "rift" needs Space Age for Vulcanus's lava shader; prototypes/tiles.lua --- falls back to "liquid" and logs if it is unavailable, rather than refusing --- to load. settings_list[#settings_list + 1] = { type = "string-setting", name = "hattorio-band-style", setting_type = "startup", default_value = "liquid", - allowed_values = { "liquid", "void", "rift" }, + allowed_values = { "liquid", "void" }, order = "c[style]-a[style]", } diff --git a/prototypes/tiles.lua b/prototypes/tiles.lua index 476d1be..c762488 100644 --- a/prototypes/tiles.lua +++ b/prototypes/tiles.lua @@ -2,28 +2,21 @@ local planets = require("mod.planets") local colours = require("mod.colours") -- Band appearance is two independent startup settings: STYLE picks the shader --- laid over the surface, COLOUR picks what that shader is tinted. They are --- orthogonal, so a glowing rift can be violet and a flat void can be ember. +-- laid over the surface, COLOUR picks what that shader is tinted. -- --- All styles clone `deepwater`. Its transitions are authored for liquid +-- Both styles clone `deepwater`. Its transitions are authored for liquid -- meeting land, which is the edge a rift wants, and it carries the only -- animated shader in the base game. +-- +-- A glowing lava-shader style was tried and removed: it read as a hazard +-- rather than as a void, and fought the calm look the rest of the mod has. local STYLE = settings.startup["hattorio-band-style"].value local COLOUR = settings.startup["hattorio-band-colour"].value --- The glowing style needs Vulcanus's lava shader, which only Space Age ships. --- Fall back rather than refuse to load, and say so in the log. -if STYLE == "rift" and not mods["space-age"] then - log("hattorio: band style 'rift' needs Space Age for the lava shader; " .. - "falling back to 'liquid'") - STYLE = "liquid" -end - -- Only the effect differs between styles; the colours come from COLOUR. local STYLE_EFFECT = { liquid = "water", void = nil, - rift = "lava-2", } local c = colours[COLOUR] or colours.violet diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 5b445c9..8390981 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -204,3 +204,59 @@ describe("band colours", function() end end) end) + +describe("band styles", function() + it("offers only styles the tiles module implements", function() + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + local allowed = src:match('name = "hattorio%-band%-style".-allowed_values = {(.-)}') + assert.is_not_nil(allowed, "no band style setting found") + + local g = assert(io.open("prototypes/tiles.lua", "r")) + local tiles = g:read("*a"); g:close() + local effects = tiles:match("local STYLE_EFFECT = {(.-)}") + assert.is_not_nil(effects, "no STYLE_EFFECT table found") + + for quoted in allowed:gmatch('"([%a]+)"') do + assert.is_not_nil(effects:find(quoted .. " ="), + "settings offer style '" .. quoted .. "' with no effect defined") + end + end) + + it("no longer offers the glowing rift", function() + -- removed after playtesting: it read as a hazard rather than a void + local f = assert(io.open("prototypes/settings.lua", "r")) + local src = f:read("*a"); f:close() + assert.is_nil(src:find("rift", 1, true)) + end) +end) + +describe("highlight contrast", function() + local colours = require("mod.colours") + + local function brightness(c) return (c[1] + c[2] + c[3]) / 3 end + + it("gives every colourway a clearly lighter highlight", function() + -- without real contrast between body and highlight the shader has nothing + -- to catch and the option looks like every other one + for name, c in pairs(colours) do + local b, h = brightness(c.body), brightness(c.highlight) + assert.is_true(h > b + 8, + string.format("%s: highlight %.0f is too close to body %.0f", name, h, b)) + end + end) + + it("keeps the colourways distinguishable from each other", function() + local seen = {} + for name, c in pairs(colours) do + for other, oc in pairs(seen) do + local d = math.abs(c.highlight[1] - oc.highlight[1]) + + math.abs(c.highlight[2] - oc.highlight[2]) + + math.abs(c.highlight[3] - oc.highlight[3]) + assert.is_true(d > 30, + name .. " and " .. other .. " have near-identical highlights") + end + seen[name] = c + end + end) +end) diff --git a/wiki/Settings.md b/wiki/Settings.md index 928ae2f..1c129e4 100644 --- a/wiki/Settings.md +++ b/wiki/Settings.md @@ -6,7 +6,7 @@ Two settings per planet, both fixed when the world is created. |---|---|---| | Cell size (centre to vertex, in tiles) | **41** (normal) | 15, 26, 41, 52 | | Band width (tiles) | **3** (wide) | 1, 2, 3, 4 | -| Band appearance | Dark liquid | liquid, void, glowing rift | +| Band appearance | Dark liquid | liquid, void | | Band colour | Deep violet | violet, cold, oily, ember, void black | Cell size and band width are dropdowns, labelled with how hard each makes the @@ -46,9 +46,7 @@ Two independent settings: what the bands are made of, and what colour they are. Any combination works. **Style** picks the surface. *Dark liquid* is the default: animated, it moves -and oozes. *Void* is flat and still, a hole rather than a liquid. *Glowing -rift* uses the lava shader and needs Space Age; without it the mod falls back -to dark liquid and says so in the log. +and oozes. *Void* is flat and still, a hole rather than a liquid. **Colour** tints it. Every option is near-black on purpose, so the bands read as depth rather than paint — what separates them is the highlight the surface From 91969f744bbf7fdbb41b1f8191183de1fb6d18e9 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 14:57:19 +0200 Subject: [PATCH 12/17] fix(locale): remove semicolons from setting descriptions Factorio's .cfg format documents ';' as a comment marker but does not say whether it is honoured mid-line. Vanilla never risks it: across 7,301 English values, zero contain a semicolon, while '#' appears four times. Cheaper to avoid than to depend on. Adds locale guards for the failure modes that are silent in-game -- a semicolon in a value, whitespace before '=' (which defines a key that never matches), duplicate keys, and keys outside any section. --- locale/en/hattorio.cfg | 4 +- spec/locale_spec.lua | 87 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 spec/locale_spec.lua diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index 50f8709..0865881 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -65,8 +65,8 @@ hattorio-band-width-aquilo-3=3 tiles (wide) hattorio-band-width-aquilo-4=4 tiles (very wide) [mod-setting-description] -hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes; void is a flat, still hole. -hattorio-band-colour=What colour the bands are tinted. Every option is near-black; they differ in the highlight the surface catches at its edges. Reflected cells, about one in eight, are tinted more strongly so you can pick them out. +hattorio-band-style=What the bands between cells are made of. Dark liquid moves and oozes. Void is a flat, still hole. +hattorio-band-colour=What colour the bands are tinted. Every option is near-black. What separates them is the highlight the surface catches at its edges. Reflected cells, about one in eight, are tinted more strongly so you can pick them out. hattorio-richness-override=Multiplier applied to ore in newly generated chunks, replacing the automatic compensation for ore the bands put out of reach. Leave at 0 to compensate automatically from your cell size and band width. hattorio-show-outline=Draw a line along the centre of each band. Affects only your own view. hattorio-hat-size-nauvis=How large each buildable cell is. Smaller cells are harder: less room, and only smaller blueprints stay reusable. 15 gives about 116 tiles per cell, 41 about 944, 52 about 1,584. diff --git a/spec/locale_spec.lua b/spec/locale_spec.lua new file mode 100644 index 0000000..46a33f2 --- /dev/null +++ b/spec/locale_spec.lua @@ -0,0 +1,87 @@ +-- Guards on the locale files. +-- +-- These failures are all silent in-game: a mangled value shows as truncated +-- text, a mangled key shows as the raw key like +-- "hattorio-band-colour-violet". None of them crash, so none of them announce +-- themselves. + +local function locale_files() + -- busted runs from the repo root, and there is only the one language today + return { "locale/en/hattorio.cfg" } +end + +local function read(path) + local f = assert(io.open(path, "r"), "cannot open " .. path) + local s = f:read("*a") + f:close() + return s +end + +describe("locale", function() + it("never puts a semicolon in a value", function() + -- Factorio's .cfg format documents ';' as a comment marker. Whether it is + -- honoured mid-line is undocumented, and vanilla never risks it: zero of + -- 7,301 vanilla English values contain one, while '#' appears four times. + -- Cheaper to avoid than to depend on. + for _, path in ipairs(locale_files()) do + local n = 0 + for line in read(path):gmatch("[^\n]+") do + n = n + 1 + if not line:match("^%s*[;#]") and not line:match("^%s*%[") then + assert.is_nil(line:find(";", 1, true), + path .. ":" .. n .. " has a semicolon in a value: " .. line) + end + end + end + end) + + it("has no whitespace around the equals sign in keys", function() + -- "title =Value" defines the key "title " with a trailing space, which + -- then never matches. Factorio gives no warning. + for _, path in ipairs(locale_files()) do + local n = 0 + for line in read(path):gmatch("[^\n]+") do + n = n + 1 + if line:find("=", 1, true) and not line:match("^%s*[;#]") + and not line:match("^%s*%[") then + assert.is_nil(line:match("^[^=]*%s="), + path .. ":" .. n .. " has whitespace before '=': " .. line) + end + end + end + end) + + it("declares no key twice", function() + for _, path in ipairs(locale_files()) do + local seen, section = {}, "" + for line in read(path):gmatch("[^\n]+") do + local header = line:match("^%s*%[(.-)%]%s*$") + if header then + section = header + elseif not line:match("^%s*[;#]") then + local key = line:match("^([^=]+)=") + if key then + local full = section .. "." .. key + assert.is_nil(seen[full], "duplicate key " .. full .. " in " .. path) + seen[full] = true + end + end + end + end + end) + + it("puts every key inside a section", function() + for _, path in ipairs(locale_files()) do + local section, n = nil, 0 + for line in read(path):gmatch("[^\n]+") do + n = n + 1 + if line:match("^%s*%[(.-)%]%s*$") then + section = line + elseif line:find("=", 1, true) and not line:match("^%s*[;#]") then + assert.is_not_nil(section, + path .. ":" .. n .. " defines a key before any [section]") + end + end + end + end) +end) From 55078823280598964d62d3250fd62e86078f433d Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 15:10:46 +0200 Subject: [PATCH 13/17] feat(mod): observability for richness, and a mid-save warning The ore richness override could not be verified by observation: 0 means 'derive it from cell size and band width', and nothing reported what that derivation produced or whether an override took effect. Surface init now logs the effective multiplier and its source, and /hattorio-info reports the geometry and richness in force. Both read the same Surface.richness(), so the number shown is the number applied. Also warns when the mod is added to a world that already has terrain: existing chunks keep vanilla ground forever, leaving a permanent boundary that nothing can fix retroactively. --- control.lua | 1 + locale/en/hattorio.cfg | 3 +++ mod/commands.lua | 37 +++++++++++++++++++++++++++++++++++++ mod/surface.lua | 23 +++++++++++++++++++++++ mod/terrain.lua | 38 +++++++++++++++++++++++++++++--------- 5 files changed, 93 insertions(+), 9 deletions(-) create mode 100644 mod/commands.lua diff --git a/control.lua b/control.lua index d51bb12..39a26d1 100644 --- a/control.lua +++ b/control.lua @@ -1 +1,2 @@ require("mod.terrain") +require("mod.commands") diff --git a/locale/en/hattorio.cfg b/locale/en/hattorio.cfg index 0865881..976e929 100644 --- a/locale/en/hattorio.cfg +++ b/locale/en/hattorio.cfg @@ -80,5 +80,8 @@ hattorio-band-width-gleba=How wide the unbuildable band between cells is. hattorio-hat-size-aquilo=How large each buildable cell is. Smaller cells are harder. hattorio-band-width-aquilo=How wide the unbuildable band between cells is. +[command-help] +hattorio-info=Report the cell size, band width and ore richness in force on this surface. + [tile-name] hattorio-band=Cell boundary diff --git a/mod/commands.lua b/mod/commands.lua new file mode 100644 index 0000000..e2a5691 --- /dev/null +++ b/mod/commands.lua @@ -0,0 +1,37 @@ +-- /hattorio-info -- what geometry and richness are actually in force. +-- +-- Exists because several of this mod's settings have effects that cannot be +-- observed directly. "Ore richness override = 0" means "derive it from cell +-- size and band width", and there is otherwise no way to see what that +-- derivation produced, or whether an override took effect at all. + +local Surface = require("mod.surface") + +local function report(command) + local player = command.player_index and game.get_player(command.player_index) + local function say(msg) + if player then player.print(msg) else game.print(msg) end + end + + local surface = player and player.surface or game.surfaces[1] + if not Surface.is_tiled(surface) then + say("hattorio: " .. surface.name .. " is not tiled by this mod.") + return + end + + local g = Surface.get(surface) + local mult, source = Surface.richness(g) + + say(string.format("hattorio on %s:", surface.name)) + say(string.format(" cell size %d tiles, band %d tiles", g.size, g.band)) + say(string.format(" substitution depth %d", g.depth)) + say(string.format(" ore richness x%.2f (%s)", mult, source)) + if source == "automatic" then + say(" set the ore richness override above 0 to replace that") + end + say(" geometry is fixed for this surface and unaffected by later " .. + "settings changes") +end + +commands.add_command("hattorio-info", + { "command-help.hattorio-info" }, report) diff --git a/mod/surface.lua b/mod/surface.lua index 0b61012..0116d41 100644 --- a/mod/surface.lua +++ b/mod/surface.lua @@ -48,9 +48,32 @@ function Surface.init(surface) g.mirror_tile = "hattorio-band-" .. planet .. "-mirror" storage.surfaces[surface.index] = g + + local mult, source = Surface.richness(g) + log(string.format( + "hattorio: %s initialised -- cell size %d, band %d, depth %d, " .. + "ore richness x%.2f (%s)", + planet, g.size, g.band, g.depth, mult, source)) + return g end +--- The ore richness multiplier in force for a surface, and where it came from. +-- +-- Shared by terrain generation and the /hattorio-info command so the number a +-- player is shown is the number actually applied, not a second derivation of +-- it that could drift. +-- +-- @return multiplier, source where source is "override" or "automatic" +function Surface.richness(g) + local setting = settings.global["hattorio-richness-override"] + local override = setting and setting.value or 0 + if override > 0 then + return override, "override" + end + return Config.richness_multiplier(g.size, g.band), "automatic" +end + function Surface.get(surface) storage.surfaces = storage.surfaces or {} return storage.surfaces[surface.index] or Surface.init(surface) diff --git a/mod/terrain.lua b/mod/terrain.lua index 45a5c39..6b80aa3 100644 --- a/mod/terrain.lua +++ b/mod/terrain.lua @@ -5,7 +5,6 @@ -- the tiling is derived from that geometry and rebuilt on load. local Ti = require("hat.tiling") -local Config = require("mod.config") local Surface = require("mod.surface") local Bands = require("mod.bands") local Outline = require("mod.outline") @@ -72,13 +71,7 @@ end local function compensate_resources(surface, area) local g = Surface.get(surface) if not g then return end - -- A map setting of 0 means "compensate automatically"; anything else - -- replaces the derived value outright. - local override = settings.global["hattorio-richness-override"] - local mult = override and override.value or 0 - if mult <= 0 then - mult = Config.richness_multiplier(g.size, g.band) - end + local mult = Surface.richness(g) if mult == 1.0 then return end local found = surface.find_entities_filtered({ @@ -127,7 +120,34 @@ script.on_load(function() Outline.reset() end) -script.on_configuration_changed(function() +--- Warn when the mod is added to a world that already has terrain. +-- +-- Bands are painted as chunks generate, so chunks that already existed keep +-- their vanilla terrain forever. The result is a hard, permanent line between +-- the old area and everything explored afterwards. Nothing can fix that +-- retroactively without regenerating the map, so the least we can do is say +-- so rather than let it be discovered later. +local function warn_if_added_mid_save(data) + local change = data and data.mod_changes and data.mod_changes["hattorio"] + if not (change and change.old_version == nil) then return end + + for _, surface in pairs(game.surfaces) do + if Surface.is_tiled(surface) then + -- is_chunk_generated on the origin is a cheap proxy for "this world has + -- been played": a brand new surface has nothing generated yet. + if surface.is_chunk_generated({ 0, 0 }) then + game.print("[Hattorio] Added to a world that already has terrain. " .. + "Existing chunks keep their vanilla ground and will never grow " .. + "cells, so there will be a permanent boundary where you have " .. + "already explored. For the intended experience, start a new world.") + return + end + end + end +end + +script.on_configuration_changed(function(data) tilings = {} Outline.reset() + warn_if_added_mid_save(data) end) From b4138795ae7ba2ea03bc039c7ca8f90bf00ca370 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 15:14:05 +0200 Subject: [PATCH 14/17] fix(mod): use measured cell statistics, warn on severe settings Checking all sixteen dropdown combinations found two that are broken: size 15 with band 4 leaves 46 buildable tiles and a 4x4 largest square, where a drill and an assembler are both 3x3, and 15/3 leaves 11% of ore reachable. The richness formula was worst exactly there -- it floored at 20% drillable where the truth is 5%, under-compensating where compensation matters most. Since the dropdowns offer exactly sixteen combinations and all sixteen are now measured, the table replaces the fit; the fit remains only as a fallback, refitted against all sixteen points (worst error 7.5pp, down from 14.6). Severe combinations now warn on load rather than being discovered an hour in. --- mod/config.lua | 80 +++++++++++++++++++++------ mod/surface.lua | 21 ++++++++ spec/config_spec.lua | 125 +++++++++++++++++++++++-------------------- wiki/Settings.md | 23 ++++++-- 4 files changed, 171 insertions(+), 78 deletions(-) diff --git a/mod/config.lua b/mod/config.lua index 0fdb349..9829fd3 100644 --- a/mod/config.lua +++ b/mod/config.lua @@ -54,31 +54,79 @@ function Config.geometry(size, band) return { size = size, band = band, unit = unit, depth = depth, half = band / 2 } end ---- Fraction of a cell's ore a 3x3 mining drill can actually reach. +-- Measured properties of every combination the dropdowns offer. -- --- Bands cost ore twice: the tiles they occupy, and the clearance a drill needs --- near an edge. The loss turns out to be (1 - drillable) * size ~ constant for --- a given band width, so the shortfall scales as band/size. +-- These are not fitted. Each was measured by rasterising a cell's interior at +-- that size and band width and taking the worst case across all orientations: -- --- Fitted to rasterised measurements, worst error 3.8 percentage points: --- size/band measured this formula --- 20/1 0.511 0.515 --- 20/2 0.398 0.360 --- 26/2 0.522 0.508 --- 41/2 0.682 0.688 --- 41/3 0.610 0.612 --- 52/2 0.749 0.754 -function Config.drillable_fraction(size, band) +-- area buildable tiles in a cell +-- square largest axis-aligned square that fits EVERY cell, so the +-- largest blueprint that stays universally reusable +-- drillable fraction of a cell's ore a 3x3 mining drill can reach +-- +-- Exact beats fitted here: the settings offer sixteen combinations and all +-- sixteen are known. Config.fit_drillable below is only a fallback for values +-- that did not come from a dropdown. +Config.CELL_STATS = { + [15] = { [1] = { area = 113, square = 6, drillable = 0.364 }, + [2] = { area = 94, square = 5, drillable = 0.242 }, + [3] = { area = 63, square = 5, drillable = 0.108 }, + [4] = { area = 46, square = 4, drillable = 0.054 } }, + [26] = { [1] = { area = 388, square = 11, drillable = 0.610 }, + [2] = { area = 346, square = 11, drillable = 0.522 }, + [3] = { area = 294, square = 10, drillable = 0.426 }, + [4] = { area = 256, square = 9, drillable = 0.343 } }, + [41] = { [1] = { area = 1012, square = 19, drillable = 0.742 }, + [2] = { area = 939, square = 18, drillable = 0.682 }, + [3] = { area = 858, square = 17, drillable = 0.613 }, + [4] = { area = 790, square = 16, drillable = 0.557 } }, + [52] = { [1] = { area = 1670, square = 24, drillable = 0.801 }, + [2] = { area = 1581, square = 23, drillable = 0.749 }, + [3] = { area = 1473, square = 22, drillable = 0.695 }, + [4] = { area = 1385, square = 21, drillable = 0.645 } }, +} + +--- Measured properties of a cell, or nil for a combination never measured. +function Config.cell_stats(size, band) + size, band = Config.clamp(size, band) + local row = Config.CELL_STATS[size] + return row and row[band] or nil +end + +--- Fallback estimate, fitted to all sixteen measurements. +-- +-- Worst error 7.5 percentage points, against 14.6 for the earlier fit, which +-- was calibrated on six points and went badly wrong at small cells -- it +-- claimed 20% of ore was reachable at size 15 band 4 where the truth is 5.4%, +-- under-compensating precisely where compensation matters most. +function Config.fit_drillable(size, band) size, band = Config.clamp(size, band) - local d = 1 - (3.1 * band + 6.6) / size - if d < 0.2 then d = 0.2 end + local d = 1 - (2.3 * band + 7.9) / size + if d < 0.05 then d = 0.05 end if d > 1.0 then d = 1.0 end return d end ---- How much to raise ore richness to offset what bands make unreachable. +--- Fraction of a cell's ore a 3x3 mining drill can actually reach. +function Config.drillable_fraction(size, band) + local stats = Config.cell_stats(size, band) + if stats then return stats.drillable end + return Config.fit_drillable(size, band) +end + +--- How much to raise ore richness to offset what bands put out of reach. function Config.richness_multiplier(size, band) return 1 / Config.drillable_fraction(size, band) end +--- Is this combination so constrained that it is barely playable? +-- +-- A mining drill and an assembling machine are both 3x3, so a cell whose +-- largest universal square is under 5 leaves almost nothing that fits with +-- room to connect it. +function Config.is_severe(size, band) + local stats = Config.cell_stats(size, band) + return stats ~= nil and stats.square < 5 +end + return Config diff --git a/mod/surface.lua b/mod/surface.lua index 0116d41..a4f472d 100644 --- a/mod/surface.lua +++ b/mod/surface.lua @@ -55,6 +55,27 @@ function Surface.init(surface) "ore richness x%.2f (%s)", planet, g.size, g.band, g.depth, mult, source)) + -- Some combinations the dropdowns allow leave almost nothing buildable. + -- At cell size 15 with a band of 4 a cell holds 46 tiles and its largest + -- universal square is 4x4 -- a mining drill and an assembling machine are + -- both 3x3, so there is no room to connect anything. Say so once rather + -- than let it be discovered an hour in. + if Config.is_severe(g.size, g.band) then + local stats = Config.cell_stats(g.size, g.band) + log(string.format( + "hattorio: WARNING %s uses cell size %d with band %d -- only %d " .. + "buildable tiles per cell and a largest square of %dx%d. This is " .. + "close to unplayable.", planet, g.size, g.band, stats.area, + stats.square, stats.square)) + if game then + game.print(string.format( + "[Hattorio] Cell size %d with band %d leaves about %d buildable " .. + "tiles per cell, and the largest blueprint that fits every cell is " .. + "%dx%d. A mining drill is 3x3. Consider a larger cell size.", + g.size, g.band, stats.area, stats.square, stats.square)) + end + end + return g end diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 8390981..1e8eea4 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -54,21 +54,9 @@ describe("config", function() it("compensates richness more when less ore is drillable", function() local small = Config.richness_multiplier(15, 2) - local large = Config.richness_multiplier(90, 2) + local large = Config.richness_multiplier(52, 1) assert.is_true(small > large, small .. " should exceed " .. large) assert.is_true(large >= 1.0) - assert.is_true(small <= 5.0, "compensation is capped at 5x") - end) - - it("caps compensation at the smallest cells", function() - -- At size 15 / band 2 barely 15% of a cell's ore is reachable, so the - -- drillable floor of 0.2 binds and compensation saturates at 5x. That is - -- deliberate: an uncapped formula would hand out absurd richness at the - -- extreme end of the settings range. - assert.are.equal(0.2, Config.drillable_fraction(15, 2)) - assert.are.equal(5.0, Config.richness_multiplier(15, 2)) - -- the default is comfortably off the floor - assert.is_true(Config.drillable_fraction(26, 2) > 0.4) end) it("compensates more for a wider band", function() @@ -82,68 +70,87 @@ describe("config", function() end) -describe("richness calibration", function() - - - -- Measured by rasterising a cell's interior and testing where a 3x3 drill - -- fits, worst case across all 12 orientations. If the formula drifts from - -- these, ore compensation silently becomes wrong. - local measured = { - { 20, 1, 0.511 }, { 20, 2, 0.398 }, { 26, 2, 0.522 }, - { 41, 2, 0.682 }, { 41, 3, 0.610 }, { 52, 2, 0.749 }, - } +describe("cell statistics", function() + local Config = require("mod.config") - it("predicts the measured drillable fractions", function() - for _, m in ipairs(measured) do - local size, band, want = m[1], m[2], m[3] - local got = Config.drillable_fraction(size, band) - assert.is_true(math.abs(got - want) < 0.05, - string.format("size %d band %d: predicted %.3f, measured %.3f", - size, band, got, want)) + it("has measurements for every combination the dropdowns offer", function() + -- a missing entry silently falls back to the fitted estimate, which is + -- much worse at small cells; this catches a choice added without + -- measuring it + for _, size in ipairs(Config.SIZE_CHOICES) do + for _, band in ipairs(Config.BAND_CHOICES) do + local st = Config.cell_stats(size, band) + assert.is_not_nil(st, "no measurements for size " .. size .. " band " .. band) + assert.is_true(st.area > 0) + assert.is_true(st.square > 0) + assert.is_true(st.drillable > 0 and st.drillable <= 1) + end end end) - it("gives a compensation that restores roughly vanilla yield", function() - for _, m in ipairs(measured) do - local size, band, want = m[1], m[2], m[3] - local effective = want * Config.richness_multiplier(size, band) - assert.is_true(effective > 0.85 and effective < 1.25, - string.format("size %d band %d: effective yield %.2f", size, band, effective)) + it("uses the measurement, not the fit, for offered combinations", function() + for _, size in ipairs(Config.SIZE_CHOICES) do + for _, band in ipairs(Config.BAND_CHOICES) do + assert.are.equal(Config.cell_stats(size, band).drillable, + Config.drillable_fraction(size, band)) + end end end) - it("offers only choices it can actually satisfy", function() + it("gets smaller cells and wider bands right, monotonically", function() for _, size in ipairs(Config.SIZE_CHOICES) do + local prev for _, band in ipairs(Config.BAND_CHOICES) do - local g = Config.geometry(size, band) - assert.are.equal(size, g.size) - assert.are.equal(band, g.band) + local st = Config.cell_stats(size, band) + if prev then + assert.is_true(st.area < prev.area, "wider band must shrink the cell") + assert.is_true(st.drillable < prev.drillable, + "wider band must reduce drillable ore") + end + prev = st + end + end + for _, band in ipairs(Config.BAND_CHOICES) do + local prev + for _, size in ipairs(Config.SIZE_CHOICES) do + local st = Config.cell_stats(size, band) + if prev then + assert.is_true(st.area > prev.area, "larger cells must hold more") + end + prev = st end end end) - it("declares the same dropdown values as prototypes/settings.lua", function() - local f = assert(io.open("prototypes/settings.lua", "r")) - local src = f:read("*a"); f:close() - assert.is_not_nil(src:find('default_value = "' .. Config.DEFAULT_SIZE .. '"', 1, true), - "settings.lua default size differs from Config.DEFAULT_SIZE") - assert.is_not_nil(src:find('default_value = "' .. Config.DEFAULT_BAND .. '"', 1, true), - "settings.lua default band differs from Config.DEFAULT_BAND") - for _, v in ipairs(Config.SIZE_CHOICES) do - assert.is_not_nil(src:find('"' .. v .. '"', 1, true), "size choice " .. v .. " missing") + it("compensates richness back to roughly vanilla yield", function() + for _, size in ipairs(Config.SIZE_CHOICES) do + for _, band in ipairs(Config.BAND_CHOICES) do + local st = Config.cell_stats(size, band) + local effective = st.drillable * Config.richness_multiplier(size, band) + assert.is_true(math.abs(effective - 1) < 1e-9, + string.format("size %d band %d: effective yield %.3f", size, band, effective)) + end end end) - it("has a locale label for every dropdown value", function() - local f = assert(io.open("locale/en/hattorio.cfg", "r")) - local src = f:read("*a"); f:close() - for _, v in ipairs(Config.SIZE_CHOICES) do - assert.is_not_nil(src:find("hattorio%-hat%-size%-nauvis%-" .. v .. "="), - "no label for size " .. v) - end - for _, v in ipairs(Config.BAND_CHOICES) do - assert.is_not_nil(src:find("hattorio%-band%-width%-nauvis%-" .. v .. "="), - "no label for band " .. v) + it("flags only the combinations that are barely playable", function() + -- 15/4 gives a 4x4 largest square; a drill and an assembler are both 3x3 + assert.is_true(Config.is_severe(15, 4)) + assert.is_false(Config.is_severe(15, 2)) + assert.is_false(Config.is_severe(26, 4)) + assert.is_false(Config.is_severe(41, 3)) + end) + + it("keeps the fallback fit close to the measurements", function() + -- used only for sizes that never came from a dropdown + for _, size in ipairs(Config.SIZE_CHOICES) do + for _, band in ipairs(Config.BAND_CHOICES) do + local got = Config.fit_drillable(size, band) + local want = Config.cell_stats(size, band).drillable + assert.is_true(math.abs(got - want) < 0.08, + string.format("size %d band %d: fit %.3f vs measured %.3f", + size, band, got, want)) + end end end) end) diff --git a/wiki/Settings.md b/wiki/Settings.md index 1c129e4..df8e30d 100644 --- a/wiki/Settings.md +++ b/wiki/Settings.md @@ -78,6 +78,23 @@ ones keep the geometry their terrain was built with, so nothing ever seams. ## Ore Bands cut through ore patches, and a mining drill needs a clear 3×3, so some -ore ends up unreachable — from about 40% reachable at size 26 up to 68% at -size 41. Resource richness is raised to compensate, scaled to your size -setting. +ore ends up out of reach. Richness is raised to compensate, using measured +values rather than a guess — the fraction reachable ranges from 80% at the +largest cells down to 5% at the smallest with the widest band. + +`/hattorio-info` reports the cell size, band width and the richness multiplier +actually in force on your surface, and whether it was derived automatically or +taken from the override setting. + +## Combinations to avoid + +Cell size and band width are independent dropdowns, so a few combinations are +possible but not sensible: + +| | cell | largest blueprint | reachable ore | | +|---|---|---|---|---| +| 15 / 3 | 63 tiles | 5×5 | 11% | ore very scarce | +| 15 / 4 | 46 tiles | 4×4 | 5% | close to unplayable | + +A mining drill and an assembling machine are both 3×3, so a 4×4 cell leaves no +room to connect anything. The mod warns you on load if you pick one of these. From d70db55ce2e3a921f9cbb648b9072dfedd543ab3 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 15:15:02 +0200 Subject: [PATCH 15/17] style: drop a shadowed local in the cell statistics spec --- spec/config_spec.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 1e8eea4..63c5acd 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -71,7 +71,6 @@ describe("config", function() end) describe("cell statistics", function() - local Config = require("mod.config") it("has measurements for every combination the dropdowns offer", function() -- a missing entry silently falls back to the fitted estimate, which is From 34c9cee9f2c86cc9323dd80602b0dedc71164d6c Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 15:18:34 +0200 Subject: [PATCH 16/17] docs: mark the mod playable, and Task 10 deferred rather than pending The README claimed the mod was not written yet, which stopped being true several commits ago. Multiplayer is called out as unverified in both the README and the wiki, since the determinism argument has never been confirmed with two clients. --- README.md | 7 ++++--- docs/plans/the-mod.md | 7 ++++++- wiki/Home.md | 7 ++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c6d1fe8..d9f8620 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ discovered in 2023. Cells never repeat, so neither can your blueprints. ![The hat tiling as Hattorio generates it](docs/preview/tiling.png) -> **Status: in development.** The tiling engine is finished and tested. The -> mod itself — terrain, build restrictions, settings — is not written yet. -> There is nothing to install from the mod portal today. +> **Status: playable, not yet released.** Terrain, build restrictions and +> settings all work and have been tested in game. Not on the mod portal yet, +> and **multiplayer is unverified** — the determinism argument is sound but +> has never been confirmed with two clients. Single player should be solid. ## What it does to your game diff --git a/docs/plans/the-mod.md b/docs/plans/the-mod.md index 3fa4965..2877311 100644 --- a/docs/plans/the-mod.md +++ b/docs/plans/the-mod.md @@ -1069,7 +1069,12 @@ git commit -m "feat(mod): thumbnail and distributable zip" --- -### Task 10: Multiplayer determinism check +### Task 10: Multiplayer determinism check [DEFERRED] + +> **Deferred by decision, not forgotten.** This needs a second machine and +> ideally a second operating system, which is the whole point of the check -- +> the risk being tested is divergence between platforms. It gates *release*, +> not the code, and the mod is marked single-player-only until it is done. The spec's determinism argument is from first principles — correctly-rounded IEEE operations, no `sin`/`cos`/`pow`, one VM instruction per operation. It diff --git a/wiki/Home.md b/wiki/Home.md index fdfda8b..494b696 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -8,9 +8,10 @@ alike. Your blueprints will not save you. ![The tiling](https://raw.githubusercontent.com/vetkat/hattorio/main/docs/preview/tiling.png) -> **Not playable yet.** The tiling engine is finished and tested; the mod -> around it is still being built. Nothing to install from the mod portal today. -> Watch the repo if you want to know when that changes. +> **Playable, but not released.** Everything works and has been tested in a +> real game. It is not on the mod portal yet, and multiplayer has not been +> verified — play single player for now. Watch +> [the repo](https://github.com/vetkat/hattorio) for the release. ## How it plays From e6db631fefce4cc887802714689411ca50df91f8 Mon Sep 17 00:00:00 2001 From: vetkat Date: Sun, 20 Sep 2026 15:23:54 +0200 Subject: [PATCH 17/17] ci: release pipeline that keeps the portal in step with the code info.json is the single source of truth for the version. tools/check_version.py asserts the changelog documents it, leads with it, and opens with the 99 dashes Factorio requires, and that any release tag names it. That runs on every push, so drift is caught long before a release. Pushing a v* tag verifies the version, runs the full suite under Lua 5.2, builds the zip, attaches it to a GitHub release and uploads it to the mod portal via the documented two-step API. A portal release cannot be withdrawn, so the upload is gated three ways: only on a tag or an explicit dispatch, behind a mod-portal environment that can require a reviewer, and after every other check has passed. --- .github/workflows/check.yml | 7 +- .github/workflows/release.yml | 100 +++++++++++++++++++++++++++++ Makefile | 8 ++- docs/design/toolchain-decisions.md | 31 +++++++++ tools/check_version.py | 67 +++++++++++++++++++ tools/publish_mod.sh | 47 ++++++++++++++ tools/tests/test_emit.py | 15 +++++ 7 files changed, 270 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 tools/check_version.py create mode 100755 tools/publish_mod.sh diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ab45aad..c88f371 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -17,7 +17,7 @@ jobs: - run: pip install pytest - name: verify the tiling and regenerate data run: python -m pytest tools/tests -q - - name: validate the mod manifest and changelog + - name: validate the mod manifest run: | python3 -c " import json @@ -25,10 +25,11 @@ jobs: assert d['factorio_version'] == '2.0', d['factorio_version'] for k in ('name','version','title','author','description','dependencies'): assert k in d, k - sep = open('changelog.txt').readline().rstrip('\n') - assert len(sep) == 99 and set(sep) == {'-'}, 'changelog separator must be 99 dashes' print('manifest ok:', d['name'], d['version']) " + # Catch version drift on every push, not only when a release is cut. + - name: check version consistency + run: python3 tools/check_version.py core: name: tiling core (Lua 5.2) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..55e7b42 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,100 @@ +name: release + +# Releases are cut by pushing a tag. info.json is the single source of truth +# for the version; the tag and the changelog must agree with it or nothing is +# built. That is the whole point -- a mod portal page describing a build +# nobody has is the failure being designed against. +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + publish: + description: "Also upload to the Factorio mod portal" + type: boolean + default: false + +permissions: + contents: write + +jobs: + build: + name: verify and build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: pip install pytest + + # Before anything is built, let alone published. + - name: check version consistency + run: python3 tools/check_version.py ${{ github.ref_type == 'tag' && github.ref_name || '' }} + + - name: offline pipeline tests + run: python -m pytest tools/tests -q + + # Factorio embeds Lua 5.2.1; testing on anything newer would mask the + # all-doubles behaviour the tiling core depends on. + - uses: leafo/gh-actions-lua@v10 + with: + luaVersion: "5.2" + - uses: leafo/gh-actions-luarocks@v4 + - run: luarocks install busted + - run: luarocks install luacheck + - run: busted + - run: luacheck hat mod prototypes spec + + - name: build the mod zip + run: python3 tools/package.py + + - name: name the artifact + id: zip + run: | + python3 - <<'PY' >> "$GITHUB_OUTPUT" + import json + i = json.load(open("info.json")) + print(f"path=build/{i['name']}_{i['version']}.zip") + print(f"version={i['version']}") + PY + + - uses: actions/upload-artifact@v4 + with: + name: mod-zip + path: ${{ steps.zip.outputs.path }} + if-no-files-found: error + + - name: attach to the GitHub release + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v2 + with: + files: ${{ steps.zip.outputs.path }} + generate_release_notes: true + + publish: + name: upload to the mod portal + needs: build + runs-on: ubuntu-latest + # Only on a tag, or an explicit dispatch asking for it. A portal release + # cannot be withdrawn, so this must never fire by accident. + if: github.ref_type == 'tag' || inputs.publish + # Configure this environment with a required reviewer if you want a human + # gate before anything reaches the portal. + environment: mod-portal + steps: + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 + with: + name: mod-zip + path: build + - name: upload + env: + FACTORIO_API_KEY: ${{ secrets.FACTORIO_API_KEY }} + run: | + if [ -z "$FACTORIO_API_KEY" ]; then + echo "::error::FACTORIO_API_KEY is not set; add it as a repository secret" + exit 1 + fi + sh tools/publish_mod.sh build/*.zip diff --git a/Makefile b/Makefile index 8958b8c..0aa398d 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ else RUN_BUSTED := $(BUSTED) endif -.PHONY: all test pytest lint data check preview wiki package +.PHONY: all test pytest lint data check preview wiki package version all: check @@ -40,7 +40,11 @@ preview: lint: luacheck hat mod prototypes spec -check: pytest test lint +check: version pytest test lint + +## check info.json, changelog and tag agree +version: + python3 tools/check_version.py ## build the distributable mod zip package: diff --git a/docs/design/toolchain-decisions.md b/docs/design/toolchain-decisions.md index 28e0888..d598a4b 100644 --- a/docs/design/toolchain-decisions.md +++ b/docs/design/toolchain-decisions.md @@ -94,3 +94,34 @@ Design section 7 states the three rules that make it explicit. The one deliberate exception is the descent's output accumulator, mutated in place for speed. It is local to `hat/tiling.lua` and never escapes. + +## Releasing + +`info.json` is the single source of truth for the version. Everything else +must agree with it, and `tools/check_version.py` enforces that on every push, +not only when a release is cut: + +- the changelog must have a `Version: X.Y.Z` entry for it, listed first +- the changelog must open with exactly 99 dashes, or Factorio refuses to load + the mod with an unhelpful message +- a release tag, if given, must be `v` + +A release is cut by pushing a tag: + +```bash +# bump info.json and add a changelog entry first +git tag v0.2.0 && git push origin v0.2.0 +``` + +`.github/workflows/release.yml` then verifies the version, runs the full suite +under Lua 5.2, builds the zip, attaches it to a GitHub release, and uploads it +to the Factorio mod portal. + +The portal upload needs a `FACTORIO_API_KEY` repository secret, from +https://factorio.com/profile with the *ModPortal: Upload Mods* permission. It +runs in a `mod-portal` GitHub environment, which can be given a required +reviewer so a human approves before anything is published. + +**A portal release cannot be withdrawn.** That is why every check runs before +the upload step rather than after, and why the upload never fires on a branch +push — only on a tag, or a manual dispatch that explicitly asks for it. diff --git a/tools/check_version.py b/tools/check_version.py new file mode 100644 index 0000000..24e2359 --- /dev/null +++ b/tools/check_version.py @@ -0,0 +1,67 @@ +"""Verify the version is consistent everywhere it appears. + +info.json is the single source of truth. Everything else must agree with it: +the changelog must document that version, and a release tag must name it. +Drift here is how a mod portal page ends up describing a build nobody has. + + python3 tools/check_version.py # info.json vs changelog + python3 tools/check_version.py v0.2.0 # also check a release tag +""" +from __future__ import annotations + +import json +import pathlib +import re +import sys + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SEMVER = re.compile(r"^\d+\.\d+\.\d+$") + + +def changelog_versions(text: str) -> list[str]: + return re.findall(r"^Version:\s*(\S+)\s*$", text, re.MULTILINE) + + +def main(argv: list[str]) -> int: + info = json.loads((ROOT / "info.json").read_text()) + version = info["version"] + problems = [] + + if not SEMVER.match(version): + problems.append(f"info.json version {version!r} is not X.Y.Z") + + changelog = (ROOT / "changelog.txt").read_text() + versions = changelog_versions(changelog) + if version not in versions: + problems.append( + f"changelog.txt has no 'Version: {version}' entry " + f"(found: {', '.join(versions) or 'none'})") + elif versions[0] != version: + problems.append( + f"changelog.txt leads with {versions[0]}, but info.json says {version}; " + "the newest release must come first") + + # Factorio refuses to load a mod whose changelog separator is malformed, + # and the message it gives is not obvious. + first = changelog.splitlines()[0] if changelog else "" + if len(first) != 99 or set(first) != {"-"}: + problems.append("changelog.txt must start with exactly 99 dashes") + + if len(argv) > 1: + tag = argv[1] + expected = f"v{version}" + if tag != expected: + problems.append(f"tag {tag} does not match info.json ({expected})") + + if problems: + for p in problems: + print(f"version check: {p}", file=sys.stderr) + return 1 + + print(f"version check: {info['name']} {version} consistent" + + (f", tag {argv[1]}" if len(argv) > 1 else "")) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/tools/publish_mod.sh b/tools/publish_mod.sh new file mode 100755 index 0000000..79537c7 --- /dev/null +++ b/tools/publish_mod.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# Upload a built mod zip to the Factorio mod portal. +# +# FACTORIO_API_KEY=... sh tools/publish_mod.sh build/hattorio_0.1.0.zip +# +# The key comes from https://factorio.com/profile and needs the +# "ModPortal: Upload Mods" permission. +# +# Two steps, per https://wiki.factorio.com/Mod_upload_API : +# 1. POST the mod name to init_upload, which returns a one-shot upload_url +# 2. POST the zip to that URL +# +# A portal release cannot be withdrawn once accepted, so every check that can +# run before this point should already have run. +set -e + +ZIP="$1" +[ -n "$ZIP" ] || { echo "usage: publish_mod.sh " >&2; exit 2; } +[ -f "$ZIP" ] || { echo "no such file: $ZIP" >&2; exit 2; } +[ -n "$FACTORIO_API_KEY" ] || { echo "FACTORIO_API_KEY is not set" >&2; exit 2; } + +MOD=$(python3 -c "import json;print(json.load(open('info.json'))['name'])") +VERSION=$(python3 -c "import json;print(json.load(open('info.json'))['version'])") +echo "publishing $MOD $VERSION from $ZIP" + +INIT=$(curl -sS -X POST \ + -H "Authorization: Bearer $FACTORIO_API_KEY" \ + -F "mod=$MOD" \ + https://mods.factorio.com/api/v2/mods/releases/init_upload) + +UPLOAD_URL=$(printf '%s' "$INIT" | python3 -c " +import json, sys +d = json.load(sys.stdin) +if 'upload_url' not in d: + sys.exit('init_upload failed: %s -- %s' % (d.get('error', '?'), d.get('message', d))) +print(d['upload_url']) +") + +RESULT=$(curl -sS -X POST -F "file=@$ZIP" "$UPLOAD_URL") + +printf '%s' "$RESULT" | python3 -c " +import json, sys +d = json.load(sys.stdin) +if not d.get('success'): + sys.exit('upload failed: %s -- %s' % (d.get('error', '?'), d.get('message', d))) +print('uploaded successfully') +" diff --git a/tools/tests/test_emit.py b/tools/tests/test_emit.py index ae87dc2..2e710cf 100644 --- a/tools/tests/test_emit.py +++ b/tools/tests/test_emit.py @@ -74,3 +74,18 @@ def test_mod_package_has_the_right_shape(): assert f"{stem}/control.lua" in names for forbidden in ("/spec/", "/docs/", "/tools/", "/wiki/", "__pycache__"): assert not any(forbidden in n for n in names), forbidden + + +def test_version_is_consistent(): + """info.json, changelog.txt and any release tag must agree. A mod portal + page describing a build nobody has is the failure this prevents.""" + r = subprocess.run([sys.executable, "tools/check_version.py"], + cwd=ROOT, capture_output=True, text=True) + assert r.returncode == 0, r.stderr + + +def test_version_check_rejects_a_mismatched_tag(): + r = subprocess.run([sys.executable, "tools/check_version.py", "v99.99.99"], + cwd=ROOT, capture_output=True, text=True) + assert r.returncode != 0, "a wrong tag must fail the check" + assert "does not match" in r.stderr