Spaces:
Sleeping
Sleeping
Download src/db.cpp from DevOG/10-bit-village: direct link, hf CLI and curl.
- Browser
- Download file 36.1 kB
-
https://huggingface.co/spaces/DevOG/10-bit-village/resolve/main/src/db.cpp
- Command line
-
hf download hf://spaces/DevOG/10-bit-village/src/db.cpp
-
curl -L -o db.cpp https://huggingface.co/spaces/DevOG/10-bit-village/resolve/main/src/db.cpp
36.1 kB
| namespace tbv { | |
| Database::Database(const std::string& path) { | |
| if (sqlite3_open(path.c_str(), &db) != SQLITE_OK) { | |
| throw std::runtime_error("Failed to open SQLite database"); | |
| } | |
| } | |
| Database::~Database() { | |
| if (db) { | |
| sqlite3_close(db); | |
| } | |
| } | |
| void Database::init_schema() { | |
| const char* sql = | |
| "PRAGMA synchronous = NORMAL;" | |
| "PRAGMA journal_mode = WAL;" | |
| "CREATE TABLE IF NOT EXISTS Ticks (" | |
| " tick_id INTEGER PRIMARY KEY" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS VillagerState (" | |
| " tick_id INTEGER," | |
| " villager_id INTEGER," | |
| " hunger INTEGER," | |
| " social INTEGER," | |
| " safety INTEGER," | |
| " pos_x INTEGER," | |
| " pos_y INTEGER," | |
| " holding_food INTEGER," | |
| " PRIMARY KEY(tick_id, villager_id)" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS MemoryGraph (" | |
| " id INTEGER PRIMARY KEY AUTOINCREMENT," | |
| " run_id TEXT," | |
| " mem_id INTEGER," | |
| " villager_id INTEGER," | |
| " tick INTEGER," | |
| " actor_id INTEGER," | |
| " importance INTEGER," | |
| " type INTEGER," | |
| " source_depth INTEGER," | |
| " origin_mem_id INTEGER," | |
| " text TEXT" | |
| ");" | |
| "CREATE INDEX IF NOT EXISTS idx_mem_villager_tick ON MemoryGraph(villager_id, tick);" | |
| "CREATE INDEX IF NOT EXISTS idx_mem_origin ON MemoryGraph(run_id, origin_mem_id);" | |
| "CREATE TABLE IF NOT EXISTS CoinedWords (" | |
| " run_id TEXT," | |
| " term TEXT," | |
| " coiner INTEGER," | |
| " birth_tick INTEGER," | |
| " PRIMARY KEY(run_id, term)" | |
| ");" | |
| // ---- MSG_062 §4 — observational tables (Step 4). run_id added on every table for | |
| // multi-run hygiene, consistent with MemoryGraph/CoinedWords above. ---- | |
| "CREATE TABLE IF NOT EXISTS HearsayChain (" | |
| " run_id TEXT," | |
| " chain_id INTEGER," | |
| " origin_mem_id INTEGER," | |
| " hop INTEGER," | |
| " src_villager INTEGER," | |
| " dst_villager INTEGER," | |
| " src_genome INTEGER," | |
| " dst_genome INTEGER," | |
| " inbound_text TEXT," | |
| " outbound_text TEXT," | |
| " content_word_delta INTEGER," | |
| " tick INTEGER" | |
| ");" | |
| "CREATE INDEX IF NOT EXISTS idx_hearsay_chain ON HearsayChain(run_id, chain_id);" | |
| "CREATE TABLE IF NOT EXISTS CognitionLog (" | |
| " run_id TEXT," | |
| " villager_id INTEGER," | |
| " tick INTEGER," | |
| " perception_hash TEXT," | |
| " raw_thought TEXT," | |
| " extracted_verb TEXT," | |
| " token_count INTEGER," | |
| " genome_pack INTEGER," | |
| " hunger INTEGER," | |
| " social INTEGER," | |
| " safety INTEGER," | |
| " seed_used INTEGER" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS BeliefSurvival (" | |
| " run_id TEXT," | |
| " memory_id INTEGER," | |
| " memory_type TEXT," | |
| " birth_tick INTEGER," | |
| " death_tick INTEGER," | |
| " max_importance INTEGER," | |
| " hop_count INTEGER," | |
| " population_reach INTEGER," | |
| " content_hash TEXT," | |
| " PRIMARY KEY(run_id, memory_id)" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS CoinageSpread (" | |
| " run_id TEXT," | |
| " term TEXT," | |
| " coiner_id INTEGER," | |
| " coiner_genome INTEGER," | |
| " birth_tick INTEGER," | |
| " adopter_id INTEGER," | |
| " adoption_tick INTEGER," | |
| " context TEXT" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS WorldDigest (" | |
| " run_id TEXT," | |
| " tick INTEGER," | |
| " seed INTEGER," | |
| " world_hash TEXT," | |
| " belief_count INTEGER," | |
| " coined_terms INTEGER," | |
| " avg_importance INTEGER," | |
| " max_hearsay_depth INTEGER," | |
| " genome_dist_hash TEXT" | |
| ");" | |
| // ---- EngineCheckpoint — full live-state serialize/restore (Step 4). One row per | |
| // run_id, overwritten (DELETE+INSERT) on every save_checkpoint() call. Arrays are raw | |
| // POD memcpy BLOBs — safe only within the same process/binary architecture, which is | |
| // the actual restart model here (HF Spaces container reboot on the same image). ---- | |
| "CREATE TABLE IF NOT EXISTS EngineCheckpoint (" | |
| " run_id TEXT PRIMARY KEY," | |
| " tick INTEGER," | |
| " next_mem_id INTEGER," | |
| " task_seq_counter INTEGER," | |
| " genomes BLOB," | |
| " needs BLOB," | |
| " pos_x BLOB," | |
| " pos_y BLOB," | |
| " holding_food BLOB," | |
| " pcg32_physics BLOB," | |
| " pcg32_cognition BLOB," | |
| " last_dream_tick BLOB," | |
| " pcg32_event BLOB," // §C1 (Run 2 Plan) -- must round-trip on restore | |
| " active_event INTEGER DEFAULT 0," | |
| " event_start_tick INTEGER DEFAULT 0" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS CheckpointMemory (" | |
| " run_id TEXT," | |
| " villager_id INTEGER," | |
| " seq INTEGER," | |
| " mem_id INTEGER," | |
| " tick INTEGER," | |
| " actor_id INTEGER," | |
| " importance INTEGER," | |
| " type INTEGER," | |
| " source_depth INTEGER," | |
| " origin_mem_id INTEGER," | |
| " text TEXT," | |
| " retell_count INTEGER DEFAULT 0," // §B3 (Run 2 Plan) -- must round-trip on restore | |
| " PRIMARY KEY(run_id, villager_id, seq)" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS CheckpointAsyncQueue (" | |
| " run_id TEXT," | |
| " seq INTEGER," | |
| " villager_id INTEGER," | |
| " fold_tick INTEGER," | |
| " task_seq_id INTEGER," | |
| " kind INTEGER," | |
| " verb INTEGER," | |
| " target INTEGER," | |
| " mtype INTEGER," | |
| " source_depth INTEGER," | |
| " importance INTEGER," | |
| " birth_tick INTEGER," | |
| " mem_id INTEGER," | |
| " origin_mem_id INTEGER," | |
| " text TEXT," | |
| " PRIMARY KEY(run_id, seq)" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS CheckpointCoinedWords (" | |
| " run_id TEXT," | |
| " term TEXT," | |
| " coiner INTEGER," | |
| " coiner_genome INTEGER," | |
| " birth_tick INTEGER," | |
| " PRIMARY KEY(run_id, term)" | |
| ");" | |
| "CREATE TABLE IF NOT EXISTS CheckpointTermAdopters (" | |
| " run_id TEXT," | |
| " term TEXT," | |
| " villager_id INTEGER," | |
| " PRIMARY KEY(run_id, term, villager_id)" | |
| ");"; | |
| char* errmsg = nullptr; | |
| if (sqlite3_exec(db, sql, nullptr, nullptr, &errmsg) != SQLITE_OK) { | |
| std::string err(errmsg); | |
| sqlite3_free(errmsg); | |
| throw std::runtime_error("Schema init failed: " + err); | |
| } | |
| // Migrate columns CREATE TABLE IF NOT EXISTS can't add to a table that already existed | |
| // pre-B3/pre-C1 (see db.h comment on ensure_column -- this is what makes cutover against | |
| // a real Run 1 db safe rather than accidentally-safe). | |
| ensure_column("CheckpointMemory", "retell_count", "INTEGER DEFAULT 0"); | |
| ensure_column("EngineCheckpoint", "pcg32_event", "BLOB"); | |
| ensure_column("EngineCheckpoint", "active_event", "INTEGER DEFAULT 0"); | |
| ensure_column("EngineCheckpoint", "event_start_tick", "INTEGER DEFAULT 0"); | |
| } | |
| void Database::ensure_column(const std::string& table, const std::string& column, | |
| const std::string& col_def_sql) { | |
| std::string pragma_sql = "PRAGMA table_info(" + table + ");"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, pragma_sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) return; | |
| bool exists = false; | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| const unsigned char* name = sqlite3_column_text(stmt, 1); // table_info col 1 = name | |
| if (name && column == reinterpret_cast<const char*>(name)) { exists = true; break; } | |
| } | |
| sqlite3_finalize(stmt); | |
| if (exists) return; | |
| std::string alter_sql = "ALTER TABLE " + table + " ADD COLUMN " + column + " " + col_def_sql + ";"; | |
| char* errmsg = nullptr; | |
| if (sqlite3_exec(db, alter_sql.c_str(), nullptr, nullptr, &errmsg) != SQLITE_OK) { | |
| std::string err(errmsg ? errmsg : "unknown error"); | |
| sqlite3_free(errmsg); | |
| throw std::runtime_error("Column migration failed (" + table + "." + column + "): " + err); | |
| } | |
| std::cout << "db: migrated " << table << " -- added column " << column << "\n" << std::flush; | |
| } | |
| void Database::log_tick_state(const WorldState& state) { | |
| // For Phase 0, we log the exact hunger of every villager to prove the PRNG physics stream varies it | |
| // Use transaction for speed | |
| sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); | |
| std::string sql = "INSERT INTO Ticks (tick_id) VALUES (?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); | |
| sqlite3_bind_int64(stmt, 1, state.current_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| sql = "INSERT INTO VillagerState (tick_id, villager_id, hunger, social, safety, pos_x, pos_y, holding_food) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); | |
| for (uint32_t i = 0; i < MAX_VILLAGERS; ++i) { | |
| sqlite3_bind_int64(stmt, 1, state.current_tick); | |
| sqlite3_bind_int(stmt, 2, i); | |
| sqlite3_bind_int(stmt, 3, state.needs[i].hunger); | |
| sqlite3_bind_int(stmt, 4, state.needs[i].social); | |
| sqlite3_bind_int(stmt, 5, state.needs[i].safety); | |
| sqlite3_bind_int(stmt, 6, state.pos_x[i]); | |
| sqlite3_bind_int(stmt, 7, state.pos_y[i]); | |
| sqlite3_bind_int(stmt, 8, state.holding_food[i] ? 1 : 0); | |
| sqlite3_step(stmt); | |
| sqlite3_reset(stmt); | |
| } | |
| sqlite3_finalize(stmt); | |
| sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr); | |
| } | |
| void Database::prune_villager_state(uint64_t keep_from_tick) { | |
| sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); | |
| const char* sqls[] = { | |
| "DELETE FROM VillagerState WHERE tick_id < ?;", | |
| "DELETE FROM Ticks WHERE tick_id < ?;", | |
| }; | |
| for (const char* sql : sqls) { | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_int64(stmt, 1, (sqlite3_int64)keep_from_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr); | |
| // reclaim disk space -- otherwise SQLite keeps the freed pages in the file for reuse, | |
| // so village.db's on-disk size would never actually shrink after a prune | |
| sqlite3_exec(db, "VACUUM;", nullptr, nullptr, nullptr); | |
| } | |
| void Database::prune_text_tables(uint64_t keep_from_tick) { | |
| sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); | |
| const char* sqls[] = { | |
| "DELETE FROM MemoryGraph WHERE tick < ?;", | |
| "DELETE FROM CognitionLog WHERE tick < ?;", | |
| "DELETE FROM HearsayChain WHERE tick < ?;", | |
| }; | |
| for (const char* sql : sqls) { | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_int64(stmt, 1, (sqlite3_int64)keep_from_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr); | |
| sqlite3_exec(db, "VACUUM;", nullptr, nullptr, nullptr); | |
| } | |
| void Database::persist_memory(const std::string& run_id, VillagerID villager_id, const MemoryEntry& m) { | |
| const char* sql = | |
| "INSERT INTO MemoryGraph (run_id, mem_id, villager_id, tick, actor_id, importance, type, source_depth, origin_mem_id, text) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 2, (int)m.mem_id); | |
| sqlite3_bind_int(stmt, 3, (int)villager_id); | |
| sqlite3_bind_int64(stmt, 4, (sqlite3_int64)m.tick); | |
| sqlite3_bind_int(stmt, 5, (int)m.actor_id); | |
| sqlite3_bind_int(stmt, 6, m.importance); | |
| sqlite3_bind_int(stmt, 7, (int)m.type); | |
| sqlite3_bind_int(stmt, 8, (int)m.source_depth); | |
| sqlite3_bind_int(stmt, 9, (int)m.origin_mem_id); | |
| sqlite3_bind_text(stmt, 10, m.text.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::register_coinage(const std::string& run_id, const std::string& term, | |
| VillagerID coiner, uint64_t birth_tick) { | |
| const char* sql = | |
| "INSERT OR IGNORE INTO CoinedWords (run_id, term, coiner, birth_tick) VALUES (?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 2, term.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 3, (int)coiner); | |
| sqlite3_bind_int64(stmt, 4, (sqlite3_int64)birth_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::log_hearsay_chain(const std::string& run_id, uint32_t chain_id, uint32_t origin_mem_id, | |
| uint8_t hop, VillagerID src, VillagerID dst, uint16_t src_genome, | |
| uint16_t dst_genome, const std::string& inbound_text, | |
| const std::string& outbound_text, int word_delta, uint64_t tick) { | |
| const char* sql = | |
| "INSERT INTO HearsayChain (run_id, chain_id, origin_mem_id, hop, src_villager, dst_villager, " | |
| "src_genome, dst_genome, inbound_text, outbound_text, content_word_delta, tick) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 2, (int)chain_id); | |
| sqlite3_bind_int(stmt, 3, (int)origin_mem_id); | |
| sqlite3_bind_int(stmt, 4, (int)hop); | |
| sqlite3_bind_int(stmt, 5, (int)src); | |
| sqlite3_bind_int(stmt, 6, (int)dst); | |
| sqlite3_bind_int(stmt, 7, (int)src_genome); | |
| sqlite3_bind_int(stmt, 8, (int)dst_genome); | |
| sqlite3_bind_text(stmt, 9, inbound_text.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 10, outbound_text.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 11, word_delta); | |
| sqlite3_bind_int64(stmt, 12, (sqlite3_int64)tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::log_cognition(const std::string& run_id, VillagerID villager_id, uint64_t tick, | |
| const std::string& perception_hash, const std::string& raw_thought, | |
| const std::string& extracted_verb, int token_count, uint16_t genome_pack, | |
| int32_t hunger, int32_t social, int32_t safety, uint64_t seed_used) { | |
| const char* sql = | |
| "INSERT INTO CognitionLog (run_id, villager_id, tick, perception_hash, raw_thought, " | |
| "extracted_verb, token_count, genome_pack, hunger, social, safety, seed_used) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 2, (int)villager_id); | |
| sqlite3_bind_int64(stmt, 3, (sqlite3_int64)tick); | |
| sqlite3_bind_text(stmt, 4, perception_hash.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 5, raw_thought.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 6, extracted_verb.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 7, token_count); | |
| sqlite3_bind_int(stmt, 8, (int)genome_pack); | |
| sqlite3_bind_int(stmt, 9, hunger); | |
| sqlite3_bind_int(stmt, 10, social); | |
| sqlite3_bind_int(stmt, 11, safety); | |
| sqlite3_bind_int64(stmt, 12, (sqlite3_int64)seed_used); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::log_belief_birth(const std::string& run_id, uint32_t memory_id, MemType type, | |
| uint64_t birth_tick, int32_t importance, uint8_t hop_count, | |
| uint64_t content_hash) { | |
| const char* sql = | |
| "INSERT INTO BeliefSurvival (run_id, memory_id, memory_type, birth_tick, death_tick, " | |
| "max_importance, hop_count, population_reach, content_hash) VALUES (?, ?, ?, ?, NULL, ?, ?, 1, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| static const char* names[] = {"EXPERIENCE", "HEARSAY", "DREAM"}; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 2, (int)memory_id); | |
| sqlite3_bind_text(stmt, 3, names[(int)type], -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int64(stmt, 4, (sqlite3_int64)birth_tick); | |
| sqlite3_bind_int(stmt, 5, importance); | |
| sqlite3_bind_int(stmt, 6, (int)hop_count); | |
| char hbuf[32]; snprintf(hbuf, sizeof(hbuf), "%016llx", (unsigned long long)content_hash); | |
| sqlite3_bind_text(stmt, 7, hbuf, -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::mark_belief_death(const std::string& run_id, uint32_t memory_id, uint64_t death_tick, | |
| uint32_t origin_mem_id) { | |
| int reach = 1; | |
| const char* csql = "SELECT COUNT(DISTINCT villager_id) FROM MemoryGraph WHERE run_id = ? AND origin_mem_id = ?;"; | |
| sqlite3_stmt* cstmt; | |
| if (sqlite3_prepare_v2(db, csql, -1, &cstmt, nullptr) == SQLITE_OK) { | |
| sqlite3_bind_text(cstmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(cstmt, 2, (int)origin_mem_id); | |
| if (sqlite3_step(cstmt) == SQLITE_ROW) reach = sqlite3_column_int(cstmt, 0); | |
| sqlite3_finalize(cstmt); | |
| } | |
| const char* usql = "UPDATE BeliefSurvival SET death_tick = ?, population_reach = ? WHERE run_id = ? AND memory_id = ?;"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, usql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_int64(stmt, 1, (sqlite3_int64)death_tick); | |
| sqlite3_bind_int(stmt, 2, reach); | |
| sqlite3_bind_text(stmt, 3, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 4, (int)memory_id); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::log_coinage_adoption(const std::string& run_id, const std::string& term, | |
| VillagerID coiner_id, uint16_t coiner_genome, uint64_t birth_tick, | |
| VillagerID adopter_id, uint64_t adoption_tick, | |
| const std::string& context) { | |
| const char* sql = | |
| "INSERT INTO CoinageSpread (run_id, term, coiner_id, coiner_genome, birth_tick, adopter_id, " | |
| "adoption_tick, context) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 2, term.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 3, (int)coiner_id); | |
| sqlite3_bind_int(stmt, 4, (int)coiner_genome); | |
| sqlite3_bind_int64(stmt, 5, (sqlite3_int64)birth_tick); | |
| sqlite3_bind_int(stmt, 6, (int)adopter_id); | |
| sqlite3_bind_int64(stmt, 7, (sqlite3_int64)adoption_tick); | |
| sqlite3_bind_text(stmt, 8, context.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| void Database::log_world_digest(const std::string& run_id, uint64_t tick, uint64_t seed, | |
| uint64_t world_hash, int belief_count, int coined_terms, | |
| int avg_importance, int max_hearsay_depth, uint64_t genome_dist_hash) { | |
| const char* sql = | |
| "INSERT INTO WorldDigest (run_id, tick, seed, world_hash, belief_count, coined_terms, " | |
| "avg_importance, max_hearsay_depth, genome_dist_hash) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int64(stmt, 2, (sqlite3_int64)tick); | |
| sqlite3_bind_int64(stmt, 3, (sqlite3_int64)seed); | |
| char wbuf[32]; snprintf(wbuf, sizeof(wbuf), "%016llx", (unsigned long long)world_hash); | |
| sqlite3_bind_text(stmt, 4, wbuf, -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 5, belief_count); | |
| sqlite3_bind_int(stmt, 6, coined_terms); | |
| sqlite3_bind_int(stmt, 7, avg_importance); | |
| sqlite3_bind_int(stmt, 8, max_hearsay_depth); | |
| char gbuf[32]; snprintf(gbuf, sizeof(gbuf), "%016llx", (unsigned long long)genome_dist_hash); | |
| sqlite3_bind_text(stmt, 9, gbuf, -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| // ============================================================================ | |
| // EngineCheckpoint — full live-state serialize/restore (Step 4). POD arrays are raw memcpy | |
| // BLOBs: safe because restore always happens within the same process/binary architecture | |
| // (HF Spaces container reboot on the same image), never cross-platform. | |
| // ============================================================================ | |
| void Database::save_checkpoint(const WorldState& w) { | |
| sqlite3_exec(db, "BEGIN TRANSACTION;", nullptr, nullptr, nullptr); | |
| auto del = [&](const char* table) { | |
| std::string sql = std::string("DELETE FROM ") + table + " WHERE run_id = ?;"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| }; | |
| del("EngineCheckpoint"); del("CheckpointMemory"); del("CheckpointAsyncQueue"); | |
| del("CheckpointCoinedWords"); del("CheckpointTermAdopters"); | |
| { | |
| const char* sql = | |
| "INSERT INTO EngineCheckpoint (run_id, tick, next_mem_id, task_seq_counter, genomes, needs, " | |
| "pos_x, pos_y, holding_food, pcg32_physics, pcg32_cognition, last_dream_tick, " | |
| "pcg32_event, active_event, event_start_tick) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int64(stmt, 2, (sqlite3_int64)w.current_tick); | |
| sqlite3_bind_int(stmt, 3, (int)w.next_mem_id); | |
| sqlite3_bind_int64(stmt, 4, (sqlite3_int64)w.task_seq_counter); | |
| sqlite3_bind_blob(stmt, 5, w.genomes, sizeof(w.genomes), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 6, w.needs, sizeof(w.needs), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 7, w.pos_x, sizeof(w.pos_x), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 8, w.pos_y, sizeof(w.pos_y), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 9, w.holding_food, sizeof(w.holding_food), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 10, w.pcg32_physics_state, sizeof(w.pcg32_physics_state), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 11, w.pcg32_cognition_state, sizeof(w.pcg32_cognition_state), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 12, w.last_dream_tick, sizeof(w.last_dream_tick), SQLITE_TRANSIENT); | |
| sqlite3_bind_blob(stmt, 13, &w.pcg32_event_state, sizeof(w.pcg32_event_state), SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 14, (int)w.active_event); | |
| sqlite3_bind_int64(stmt, 15, (sqlite3_int64)w.event_start_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_finalize(stmt); | |
| } | |
| { | |
| const char* sql = | |
| "INSERT INTO CheckpointMemory (run_id, villager_id, seq, mem_id, tick, actor_id, importance, " | |
| "type, source_depth, origin_mem_id, text, retell_count) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| for (uint32_t v = 0; v < MAX_VILLAGERS; ++v) { | |
| const auto& mems = w.stores[v].all(); | |
| for (size_t seq = 0; seq < mems.size(); ++seq) { | |
| const auto& m = mems[seq]; | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 2, (int)v); | |
| sqlite3_bind_int64(stmt, 3, (sqlite3_int64)seq); | |
| sqlite3_bind_int(stmt, 4, (int)m.mem_id); | |
| sqlite3_bind_int64(stmt, 5, (sqlite3_int64)m.tick); | |
| sqlite3_bind_int(stmt, 6, (int)m.actor_id); | |
| sqlite3_bind_int(stmt, 7, m.importance); | |
| sqlite3_bind_int(stmt, 8, (int)m.type); | |
| sqlite3_bind_int(stmt, 9, (int)m.source_depth); | |
| sqlite3_bind_int(stmt, 10, (int)m.origin_mem_id); | |
| sqlite3_bind_text(stmt, 11, m.text.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 12, (int)m.retell_count); | |
| sqlite3_step(stmt); | |
| sqlite3_reset(stmt); | |
| } | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| { | |
| const char* sql = | |
| "INSERT INTO CheckpointAsyncQueue (run_id, seq, villager_id, fold_tick, task_seq_id, kind, " | |
| "verb, target, mtype, source_depth, importance, birth_tick, mem_id, origin_mem_id, text) " | |
| "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| for (size_t seq = 0; seq < w.async_queue.size(); ++seq) { | |
| const auto& t = w.async_queue[seq]; | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int64(stmt, 2, (sqlite3_int64)seq); | |
| sqlite3_bind_int(stmt, 3, (int)t.villager_id); | |
| sqlite3_bind_int64(stmt, 4, (sqlite3_int64)t.fold_tick); | |
| sqlite3_bind_int64(stmt, 5, (sqlite3_int64)t.task_seq_id); | |
| sqlite3_bind_int(stmt, 6, (int)t.kind); | |
| sqlite3_bind_int(stmt, 7, (int)t.verb); | |
| sqlite3_bind_int(stmt, 8, (int)t.target); | |
| sqlite3_bind_int(stmt, 9, (int)t.mtype); | |
| sqlite3_bind_int(stmt, 10, (int)t.source_depth); | |
| sqlite3_bind_int(stmt, 11, t.importance); | |
| sqlite3_bind_int64(stmt, 12, (sqlite3_int64)t.birth_tick); | |
| sqlite3_bind_int(stmt, 13, (int)t.mem_id); | |
| sqlite3_bind_int(stmt, 14, (int)t.origin_mem_id); | |
| sqlite3_bind_text(stmt, 15, t.text.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_step(stmt); | |
| sqlite3_reset(stmt); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| { | |
| const char* sql = | |
| "INSERT INTO CheckpointCoinedWords (run_id, term, coiner, coiner_genome, birth_tick) " | |
| "VALUES (?, ?, ?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| for (const auto& kv : w.coinage_origin) { | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 2, kv.first.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 3, (int)kv.second.coiner); | |
| sqlite3_bind_int(stmt, 4, (int)kv.second.coiner_genome); | |
| sqlite3_bind_int64(stmt, 5, (sqlite3_int64)kv.second.birth_tick); | |
| sqlite3_step(stmt); | |
| sqlite3_reset(stmt); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| { | |
| const char* sql = "INSERT INTO CheckpointTermAdopters (run_id, term, villager_id) VALUES (?, ?, ?);"; | |
| sqlite3_stmt* stmt; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| for (const auto& kv : w.term_adopters) { | |
| for (VillagerID a : kv.second) { | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_text(stmt, 2, kv.first.c_str(), -1, SQLITE_TRANSIENT); | |
| sqlite3_bind_int(stmt, 3, (int)a); | |
| sqlite3_step(stmt); | |
| sqlite3_reset(stmt); | |
| } | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| sqlite3_exec(db, "COMMIT;", nullptr, nullptr, nullptr); | |
| } | |
| bool Database::load_checkpoint(WorldState& w) { | |
| sqlite3_stmt* stmt; | |
| { | |
| const char* sql = | |
| "SELECT tick, next_mem_id, task_seq_counter, genomes, needs, pos_x, pos_y, " | |
| "holding_food, pcg32_physics, pcg32_cognition, last_dream_tick, " | |
| "pcg32_event, active_event, event_start_tick " | |
| "FROM EngineCheckpoint WHERE run_id = ?;"; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return false; | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| if (sqlite3_step(stmt) != SQLITE_ROW) { sqlite3_finalize(stmt); return false; } | |
| w.current_tick = (uint64_t)sqlite3_column_int64(stmt, 0); | |
| w.next_mem_id = (uint32_t)sqlite3_column_int(stmt, 1); | |
| w.task_seq_counter = (uint64_t)sqlite3_column_int64(stmt, 2); | |
| auto copy_blob = [&](int col, void* dst, size_t expected) { | |
| const void* blob = sqlite3_column_blob(stmt, col); | |
| size_t n = (size_t)sqlite3_column_bytes(stmt, col); | |
| if (blob && n == expected) std::memcpy(dst, blob, expected); | |
| }; | |
| copy_blob(3, w.genomes, sizeof(w.genomes)); | |
| copy_blob(4, w.needs, sizeof(w.needs)); | |
| copy_blob(5, w.pos_x, sizeof(w.pos_x)); | |
| copy_blob(6, w.pos_y, sizeof(w.pos_y)); | |
| copy_blob(7, w.holding_food, sizeof(w.holding_food)); | |
| copy_blob(8, w.pcg32_physics_state, sizeof(w.pcg32_physics_state)); | |
| copy_blob(9, w.pcg32_cognition_state, sizeof(w.pcg32_cognition_state)); | |
| copy_blob(10, w.last_dream_tick, sizeof(w.last_dream_tick)); | |
| copy_blob(11, &w.pcg32_event_state, sizeof(w.pcg32_event_state)); | |
| w.active_event = (VillageEvent)sqlite3_column_int(stmt, 12); | |
| w.event_start_tick = (uint64_t)sqlite3_column_int64(stmt, 13); | |
| sqlite3_finalize(stmt); | |
| } | |
| std::vector<std::vector<MemoryEntry>> per_villager(MAX_VILLAGERS); | |
| { | |
| const char* sql = | |
| "SELECT villager_id, mem_id, tick, actor_id, importance, type, source_depth, " | |
| "origin_mem_id, text, retell_count FROM CheckpointMemory WHERE run_id = ? ORDER BY villager_id, seq ASC;"; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| uint32_t vid = (uint32_t)sqlite3_column_int(stmt, 0); | |
| MemoryEntry m; | |
| m.mem_id = (uint32_t)sqlite3_column_int(stmt, 1); | |
| m.tick = (uint64_t)sqlite3_column_int64(stmt, 2); | |
| m.actor_id = (uint32_t)sqlite3_column_int(stmt, 3); | |
| m.importance = sqlite3_column_int(stmt, 4); | |
| m.type = (MemType)sqlite3_column_int(stmt, 5); | |
| m.source_depth = (uint8_t)sqlite3_column_int(stmt, 6); | |
| m.origin_mem_id = (uint32_t)sqlite3_column_int(stmt, 7); | |
| const char* txt = (const char*)sqlite3_column_text(stmt, 8); | |
| m.text = txt ? txt : ""; | |
| m.retell_count = (uint16_t)sqlite3_column_int(stmt, 9); | |
| if (vid < MAX_VILLAGERS) per_villager[vid].push_back(std::move(m)); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| for (uint32_t v = 0; v < MAX_VILLAGERS; ++v) w.stores[v].load_raw(std::move(per_villager[v])); | |
| w.async_queue.clear(); | |
| { | |
| const char* sql = | |
| "SELECT villager_id, fold_tick, task_seq_id, kind, verb, target, mtype, " | |
| "source_depth, importance, birth_tick, mem_id, origin_mem_id, text " | |
| "FROM CheckpointAsyncQueue WHERE run_id = ? ORDER BY seq ASC;"; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| DeferredTask t; | |
| t.villager_id = (VillagerID)sqlite3_column_int(stmt, 0); | |
| t.fold_tick = (uint64_t)sqlite3_column_int64(stmt, 1); | |
| t.task_seq_id = (uint64_t)sqlite3_column_int64(stmt, 2); | |
| t.kind = (TaskKind)sqlite3_column_int(stmt, 3); | |
| t.verb = (Verb)sqlite3_column_int(stmt, 4); | |
| t.target = (VillagerID)sqlite3_column_int(stmt, 5); | |
| t.mtype = (MemType)sqlite3_column_int(stmt, 6); | |
| t.source_depth = (uint8_t)sqlite3_column_int(stmt, 7); | |
| t.importance = sqlite3_column_int(stmt, 8); | |
| t.birth_tick = (uint64_t)sqlite3_column_int64(stmt, 9); | |
| t.mem_id = (uint32_t)sqlite3_column_int(stmt, 10); | |
| t.origin_mem_id = (uint32_t)sqlite3_column_int(stmt, 11); | |
| const char* txt = (const char*)sqlite3_column_text(stmt, 12); | |
| t.text = txt ? txt : ""; | |
| w.async_queue.push_back(std::move(t)); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| w.coinage_origin.clear(); w.coined_words.clear(); | |
| { | |
| const char* sql = "SELECT term, coiner, coiner_genome, birth_tick FROM CheckpointCoinedWords WHERE run_id = ?;"; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| std::string term = (const char*)sqlite3_column_text(stmt, 0); | |
| WorldState::CoinageOrigin o; | |
| o.coiner = (VillagerID)sqlite3_column_int(stmt, 1); | |
| o.coiner_genome = (uint16_t)sqlite3_column_int(stmt, 2); | |
| o.birth_tick = (uint64_t)sqlite3_column_int64(stmt, 3); | |
| w.coinage_origin[term] = o; | |
| w.coined_words.insert(term); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| w.term_adopters.clear(); | |
| { | |
| const char* sql = "SELECT term, villager_id FROM CheckpointTermAdopters WHERE run_id = ?;"; | |
| sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr); | |
| sqlite3_bind_text(stmt, 1, w.run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| while (sqlite3_step(stmt) == SQLITE_ROW) { | |
| std::string term = (const char*)sqlite3_column_text(stmt, 0); | |
| VillagerID vid = (VillagerID)sqlite3_column_int(stmt, 1); | |
| w.term_adopters[term].insert(vid); | |
| } | |
| sqlite3_finalize(stmt); | |
| } | |
| return true; | |
| } | |
| int Database::count_rows(const std::string& table, const std::string& run_id) { | |
| std::string sql = "SELECT COUNT(*) FROM " + table + " WHERE run_id = ?;"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) return -1; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| int n = (sqlite3_step(stmt) == SQLITE_ROW) ? sqlite3_column_int(stmt, 0) : -1; | |
| sqlite3_finalize(stmt); | |
| return n; | |
| } | |
| int Database::count_memories(const std::string& run_id) { | |
| const char* sql = "SELECT COUNT(*) FROM MemoryGraph WHERE run_id = ?;"; | |
| sqlite3_stmt* stmt; | |
| if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) return -1; | |
| sqlite3_bind_text(stmt, 1, run_id.c_str(), -1, SQLITE_TRANSIENT); | |
| int n = (sqlite3_step(stmt) == SQLITE_ROW) ? sqlite3_column_int(stmt, 0) : -1; | |
| sqlite3_finalize(stmt); | |
| return n; | |
| } | |
| // Live-DB -> staging-file copy via SQLite's Online Backup API. Never called from a signal | |
| // handler directly (not async-signal-safe) — the --serve loop calls this at a tick boundary | |
| // in response to a flag a handler set. Overwrites dest_path with a fresh single-step copy. | |
| bool Database::backup_to(const std::string& dest_path) { | |
| sqlite3* dest = nullptr; | |
| if (sqlite3_open(dest_path.c_str(), &dest) != SQLITE_OK) { | |
| if (dest) sqlite3_close(dest); | |
| return false; | |
| } | |
| sqlite3_backup* bk = sqlite3_backup_init(dest, "main", db, "main"); | |
| bool ok = false; | |
| if (bk) { | |
| ok = (sqlite3_backup_step(bk, -1) == SQLITE_DONE); | |
| sqlite3_backup_finish(bk); | |
| } | |
| sqlite3_close(dest); | |
| return ok; | |
| } | |
| } // namespace tbv | |