-- Migration 000019: Wider spiral world — ~3× spacing vs 000018, four new towns on the ring -- (midpoints along progression segments Willowdale→Thornwatch→Ashengard→Redcliff→Boghollow), -- full ring roads + road_waypoints recomputed from town centers (same rules as 000017/000018). -- Level bands (1–40, non-overlapping) follow road order by level_min. UPDATE towns SET level_min = 1, level_max = 4 WHERE name = 'Willowdale'; UPDATE towns SET level_min = 9, level_max = 12 WHERE name = 'Thornwatch'; UPDATE towns SET level_min = 17, level_max = 20 WHERE name = 'Ashengard'; UPDATE towns SET level_min = 25, level_max = 27 WHERE name = 'Redcliff'; UPDATE towns SET level_min = 31, level_max = 33 WHERE name = 'Boghollow'; UPDATE towns SET level_min = 34, level_max = 37 WHERE name = 'Cinderkeep'; UPDATE towns SET level_min = 38, level_max = 40 WHERE name = 'Starfall'; -- Positions: 000018 layout scaled ×3 from origin (stronger separation); new towns at segment midpoints. UPDATE towns SET world_x = 7860, world_y = 2400 WHERE name = 'Willowdale'; UPDATE towns SET world_x = 8778, world_y = 3174 WHERE name = 'Thornwatch'; UPDATE towns SET world_x = 8697, world_y = 4752 WHERE name = 'Ashengard'; UPDATE towns SET world_x = 7197, world_y = 6168 WHERE name = 'Redcliff'; UPDATE towns SET world_x = 4605, world_y = 6378 WHERE name = 'Boghollow'; UPDATE towns SET world_x = 1899, world_y = 4713 WHERE name = 'Cinderkeep'; UPDATE towns SET world_x = 393, world_y = 1980 WHERE name = 'Starfall'; INSERT INTO towns (name, biome, world_x, world_y, radius, level_min, level_max) VALUES ('Mossharbor', 'meadow', 8319, 2787, 14.0, 5, 8), ('Emberwell', 'forest', 8738, 3963, 15.0, 13, 16), ('Frostmark', 'ruins', 7947, 5460, 14.0, 21, 24), ('Duskwatch', 'swamp', 5901, 6273, 14.0, 28, 30) ON CONFLICT (name) DO NOTHING; -- NPCs for new settlements (idempotent). INSERT INTO npcs (town_id, name, type, offset_x, offset_y) SELECT t.id, v.npc_name, v.npc_type, v.ox, v.oy FROM (VALUES ('Mossharbor', 'Harbor-ward Lissa', 'quest_giver', -2.5::double precision, 1.0::double precision), ('Mossharbor', 'Dock Trader Milo', 'merchant', 2.5, 0.0), ('Emberwell', 'Ranger Kess', 'quest_giver', 1.0, -2.0), ('Emberwell', 'Ember Outfitter', 'merchant', -2.0, 2.0), ('Frostmark', 'Warden Torvik', 'quest_giver', -1.5, 1.5), ('Frostmark', 'Relic Peddler', 'merchant', 2.0, -1.0), ('Duskwatch', 'Sister Morah', 'quest_giver', 0.0, 2.5), ('Duskwatch', 'Bog Imports', 'merchant', -2.5, -1.0) ) AS v(town_name, npc_name, npc_type, ox, oy) JOIN towns t ON t.name = v.town_name WHERE NOT EXISTS ( SELECT 1 FROM npcs n WHERE n.town_id = t.id AND n.name = v.npc_name ); -- Quest level windows: align with new town bands and progression. UPDATE quests SET min_level = 1, max_level = 4 WHERE title = 'Wolf Cull'; UPDATE quests SET min_level = 1, max_level = 8 WHERE title = 'Deliver to Thornwatch'; UPDATE quests SET min_level = 2, max_level = 8 WHERE title = 'Boar Hunt'; UPDATE quests SET min_level = 9, max_level = 12 WHERE title IN ('Spider Infestation', 'Spider Fang Collection'); UPDATE quests SET min_level = 9, max_level = 14 WHERE title = 'Forest Patrol'; UPDATE quests SET min_level = 13, max_level = 20 WHERE title IN ('Undead Purge', 'Ancient Relics', 'Report to Redcliff'); UPDATE quests SET min_level = 21, max_level = 27 WHERE title IN ('Orc Raider Cleanup', 'Ore Samples'); UPDATE quests SET min_level = 28, max_level = 33 WHERE title IN ('Swamp Creatures', 'Venomous Harvest', 'Message to Cinderkeep'); UPDATE quests SET min_level = 34, max_level = 37 WHERE title IN ('Demon Slayer', 'Infernal Cores'); UPDATE quests SET min_level = 38, max_level = 40 WHERE title IN ('Titan''s Challenge', 'Void Fragments', 'Full Circle'); -- Replace road graph: bidirectional ring in level order + wrap Starfall → Willowdale. DELETE FROM road_waypoints; DELETE FROM roads; INSERT INTO roads (from_town_id, to_town_id, distance) SELECT f.id, t.id, 1000.0 FROM (VALUES ('Willowdale', 'Mossharbor'), ('Mossharbor', 'Thornwatch'), ('Thornwatch', 'Emberwell'), ('Emberwell', 'Ashengard'), ('Ashengard', 'Frostmark'), ('Frostmark', 'Redcliff'), ('Redcliff', 'Duskwatch'), ('Duskwatch', 'Boghollow'), ('Boghollow', 'Cinderkeep'), ('Cinderkeep', 'Starfall'), ('Starfall', 'Willowdale') ) AS seg(from_name, to_name) JOIN towns f ON f.name = seg.from_name JOIN towns t ON t.name = seg.to_name; INSERT INTO roads (from_town_id, to_town_id, distance) SELECT t.id, f.id, 1000.0 FROM (VALUES ('Willowdale', 'Mossharbor'), ('Mossharbor', 'Thornwatch'), ('Thornwatch', 'Emberwell'), ('Emberwell', 'Ashengard'), ('Ashengard', 'Frostmark'), ('Frostmark', 'Redcliff'), ('Redcliff', 'Duskwatch'), ('Duskwatch', 'Boghollow'), ('Boghollow', 'Cinderkeep'), ('Cinderkeep', 'Starfall'), ('Starfall', 'Willowdale') ) AS seg(from_name, to_name) JOIN towns f ON f.name = seg.from_name JOIN towns t ON t.name = seg.to_name; -- Canonical polylines (same segment rule as Go road_graph / 000017 — no jitter). INSERT INTO road_waypoints (road_id, seq, x, y) SELECT r.id, gs.seq, CASE WHEN gs.seq = 0 THEN f.world_x WHEN gs.seq = seg.nseg THEN t.world_x ELSE f.world_x + (t.world_x - f.world_x) * (gs.seq::double precision / seg.nseg::double precision) END, CASE WHEN gs.seq = 0 THEN f.world_y WHEN gs.seq = seg.nseg THEN t.world_y ELSE f.world_y + (t.world_y - f.world_y) * (gs.seq::double precision / seg.nseg::double precision) END FROM roads r INNER JOIN towns f ON f.id = r.from_town_id INNER JOIN towns t ON t.id = r.to_town_id CROSS JOIN LATERAL ( SELECT GREATEST( 1, FLOOR( SQRT(POWER(t.world_x - f.world_x, 2) + POWER(t.world_y - f.world_y, 2)) / 20.0 )::integer ) AS nseg ) seg CROSS JOIN LATERAL generate_series(0, seg.nseg) AS gs(seq);