diff --git a/include/rc_runtime_types.h b/include/rc_runtime_types.h index 81ede5a9..e1779706 100644 --- a/include/rc_runtime_types.h +++ b/include/rc_runtime_types.h @@ -173,6 +173,8 @@ enum { RC_OPERATOR_SUB, RC_OPERATOR_SUB_PARENT, /* internal use */ + RC_OPERATOR_ADD_ACCUMULATOR, /* internal use */ + RC_OPERATOR_SUB_ACCUMULATOR, /* internal use */ RC_OPERATOR_INDIRECT_READ /* internal use */ }; diff --git a/src/rcheevos/condition.c b/src/rcheevos/condition.c index 320ca0a6..855ea8ac 100644 --- a/src/rcheevos/condition.c +++ b/src/rcheevos/condition.c @@ -374,7 +374,7 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand)); } - parse->addsource_oper = RC_OPERATOR_ADD; + parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR; parse->indirect_parent.type = RC_OPERAND_NONE; break; @@ -388,13 +388,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t /* type determined by parent */ const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS; - if (parse->addsource_oper == RC_OPERATOR_ADD && !rc_operand_is_memref(&parse->addsource_parent)) { + if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR && !rc_operand_is_memref(&parse->addsource_parent)) { /* if the previous element was a constant we have to turn it into a memref by adding zero */ rc_modified_memref_t* memref; rc_operand_t zero; rc_operand_set_const(&zero, 0); memref = rc_alloc_modified_memref(parse, - parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD, &zero); + parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD_ACCUMULATOR, &zero); parse->addsource_parent.value.memref = (rc_memref_t*)memref; parse->addsource_parent.type = RC_OPERAND_ADDRESS; } @@ -414,13 +414,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t } /* subtract the condition from the chain */ - parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB : RC_OPERATOR_SUB_PARENT; + parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT; rc_condition_convert_to_operand(condition, &cond_operand, parse); rc_operand_addsource(&cond_operand, parse, new_size); memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand)); /* indicate the next value can be added to the chain */ - parse->addsource_oper = RC_OPERATOR_ADD; + parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR; } parse->indirect_parent.type = RC_OPERAND_NONE; @@ -465,6 +465,9 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t default: if (parse->addsource_parent.type != RC_OPERAND_NONE) { /* type determined by leaf */ + if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR) + parse->addsource_oper = RC_OPERATOR_ADD; + rc_operand_addsource(&condition->operand1, parse, condition->operand1.size); condition->operand1.is_combining = 1; diff --git a/src/rcheevos/memref.c b/src/rcheevos/memref.c index f1e92102..ded6556c 100644 --- a/src/rcheevos/memref.c +++ b/src/rcheevos/memref.c @@ -729,11 +729,34 @@ uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_pee break; case RC_OPERATOR_SUB_PARENT: + /* sub parent is "-parent + modifier" */ rc_typed_value_negate(&value); rc_typed_value_add(&value, &modifier); rc_typed_value_convert(&value, memref->memref.value.type); break; + case RC_OPERATOR_SUB_ACCUMULATOR: + rc_typed_value_negate(&modifier); + /* fallthrough */ /* to case RC_OPERATOR_SUB_ACCUMULATOR */ + + case RC_OPERATOR_ADD_ACCUMULATOR: + /* when modifying the accumulator, force the modifier to match the accumulator + * type instead of promoting them both to the less restrictive type. + * + * 18 - 17.5 will result in an integer. should it be 0 or 1? + * + * default: float is less restrictive, convert both to float for combine, + * then convert to the memref type. + * (int)((float)18 - 17.5) -> (int)(0.5) -> 0 + * + * accumulator is integer: force modifier to be integer before combining + * (int)(18 - (int)17.5) -> (int)(18 - 17) -> 1 + */ + rc_typed_value_convert(&modifier, value.type); + rc_typed_value_add(&value, &modifier); + rc_typed_value_convert(&value, memref->memref.value.type); + break; + default: rc_typed_value_combine(&value, &modifier, memref->modifier_type); rc_typed_value_convert(&value, memref->memref.value.type); diff --git a/src/rcheevos/value.c b/src/rcheevos/value.c index 082475f3..865e3125 100644 --- a/src/rcheevos/value.c +++ b/src/rcheevos/value.c @@ -67,6 +67,7 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par next_clause = &self->conditions; do { /* count the number of joiners and add one to determine the number of clauses. */ + buffer[0] = 'A'; /* reset to AddSource */ done = 0; num_measured_conditions = 1; buffer_ptr = *memaddr; @@ -97,8 +98,8 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par } } while (!done); - /* if last condition is SubSource, we'll need to add a dummy condition for the Measured */ - if (buffer[0] == 'B') + /* if last condition is not AddSource, we'll need to add a dummy condition for the Measured */ + if (buffer[0] != 'A') ++num_measured_conditions; condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t, @@ -121,10 +122,18 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par for (;; ++(*memaddr)) { switch (**memaddr) { case '_': /* add next */ + *ptr = '\0'; + break; + case '$': /* maximum of */ case '\0': /* end of string */ case ':': /* end of leaderboard clause */ case ')': /* end of rich presence macro */ + /* the last condition needs to be Measured - AddSource can be changed here, + * SubSource will be handled later */ + if (buffer[0] == 'A') + buffer[0] = 'M'; + *ptr = '\0'; break; @@ -176,33 +185,34 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par return; } + rc_condition_update_parse_state(cond, parse); + *next = cond; next = &cond->next; if (**memaddr != '_') /* add next */ break; - rc_condition_update_parse_state(cond, parse); ++cond; } - /* end of clause */ - if (cond->type == RC_CONDITION_SUB_SOURCE) { - /* cannot change SubSource to Measured. add a dummy condition */ - rc_condition_update_parse_state(cond, parse); - if (parse->buffer) + /* -- end of clause -- */ + + /* clause must end in a Measured. if it doesn't, append one */ + if (cond->type != RC_CONDITION_MEASURED) { + if (!parse->buffer) + cond = &local_cond; + else ++cond; - buffer_ptr = "A:0"; + buffer_ptr = "M:0"; rc_parse_condition_internal(cond, &buffer_ptr, parse); *next = cond; next = &cond->next; + rc_condition_update_parse_state(cond, parse); } - /* convert final AddSource condition to Measured */ - cond->type = RC_CONDITION_MEASURED; - cond->next = NULL; - rc_condition_update_parse_state(cond, parse); + *next = NULL; /* finalize clause */ *next_clause = condset; diff --git a/test/rcheevos/test_condset.c b/test/rcheevos/test_condset.c index b4e3b985..b6971a7a 100644 --- a/test/rcheevos/test_condset.c +++ b/test/rcheevos/test_condset.c @@ -3125,6 +3125,34 @@ static void test_addhits_unfinished() { ASSERT_NUM_EQUALS(condset->num_other_conditions, 2); } +static void test_addhits_float_coercion() { + uint8_t ram[] = { 0x00, 0x06, 0x34, 0xAB, 0x00, 0x00, 0xC0, 0x3F }; /* fF0004 = 1.5 */ + memory_t memory; + rc_condset_t* condset; + rc_memrefs_t memrefs; + char buffer[2048]; + + memory.ram = ram; + memory.size = sizeof(ram); + + /* 0 + float(4) * 10 - prev(float(4)) * 10 + 0 + 0 = 1 */ + assert_parse_condset(&condset, &memrefs, buffer, "A:0_A:fF0004*10_B:dfF0004*10_A:0_0=1"); + + /* float(4) = 1.5, prev(float(4)) = 0.0. 0+15-0+0+0=1 = false */ + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* float(4) = 2.0, prev(float(4)) = 1.5. 0+20-15+0+0=1 = false */ + ram[6] = 0x00; + ram[7] = 0x40; + assert_evaluate_condset(condset, memrefs, &memory, 0); + + /* float(4) = 2.1, prev(float(4)) = 2.0. 0+21-20+0+0=1 = true */ + ram[6] = 0x06; + ram[5] = 0x66; + ram[4] = 0x66; + assert_evaluate_condset(condset, memrefs, &memory, 1); +} + static void test_andnext() { uint8_t ram[] = {0x00, 0x12, 0x34, 0xAB, 0x56}; memory_t memory; @@ -5012,6 +5040,7 @@ void test_condset(void) { TEST(test_subhits); TEST(test_subhits_below_zero); TEST(test_addhits_unfinished); + TEST(test_addhits_float_coercion) /* andnext */ TEST(test_andnext); diff --git a/test/rcheevos/test_value.c b/test/rcheevos/test_value.c index d0592e3c..0cffe9bf 100644 --- a/test/rcheevos/test_value.c +++ b/test/rcheevos/test_value.c @@ -608,6 +608,82 @@ static void test_typed_value_negation() { TEST_PARAMS6(test_typed_value_negate, RC_VALUE_TYPE_FLOAT, 0, -2.7, RC_VALUE_TYPE_FLOAT, 0, 2.7); } +static void test_addhits_float_coercion() { + rc_value_t* self; + uint8_t ram[] = { 0x00, 0x06, 0x34, 0xAB, 0x00, 0x00, 0xC0, 0x3F }; /* fF0004 = 1.5 */ + memory_t memory; + char buffer[2048]; + /* measured(tally(0, (0 + float(4) * 10 - prev(float(4)) * 10) == 1)) */ + const char* memaddr = "A:0_A:fF0004*10_B:dfF0004*10_C:0=1_M:0=1"; + int ret; + + memory.ram = ram; + memory.size = sizeof(ram); + + ret = rc_value_size(memaddr); + ASSERT_NUM_GREATER_EQUALS(ret, 0); + + self = rc_parse_value(buffer, memaddr, NULL, 0); + ASSERT_PTR_NOT_NULL(self); + + /* The 0+ at the start of the expression changes the accumulator to an integer, + * so when float(4)*10 is added and prev(float(4))*10 is subtracted, they'll also + * be converted to integers before they're combined. + */ + + /* float(4) = 1.5, prev(float(4)) = 0.0. 0+15-0=1 is false => 0 */ + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 0); + + /* float(4) = 1.75, prev(float(4)) = 1.5. 0+17-15 => 2 => 2=1 is false => 0 */ + ram[7] = 0x3f; ram[6] = 0xe0; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 0); + + /* float(4) = 1.82, prev(float(4)) = 1.75. 0+18-17 => 1 => 1=1 is true => 1 */ + ram[6] = 0xe8; ram[5] = 0xf5; ram[4] = 0xc3; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 1); + + /* float(4) = 2.06, prev(float(4)) = 1.82. 0+20-18 => 2 => 2=1 is false => 1 */ + ram[7] = 0x40; ram[6] = 0x03; ram[5] = 0xd7; ram[4] = 0x0a; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 1); +} + +static void test_addhits_float_coercion_remembered() { + rc_value_t* self; + uint8_t ram[] = { 0x00, 0x06, 0x34, 0xAB, 0x00, 0x00, 0xC0, 0x3F }; /* fF0004 = 1.5 */ + memory_t memory; + char buffer[2048]; + /* measured(tally(0, remembered(0 - prev(float(4)) * 10) + (0 + float(4) * 10) == 1)) */ + const char* memaddr = "A:0_B:dfF0004*10_K:0_A:0_A:fF0004*10_C:{recall}=1_M:0=1"; + int ret; + + memory.ram = ram; + memory.size = sizeof(ram); + + ret = rc_value_size(memaddr); + ASSERT_NUM_GREATER_EQUALS(ret, 0); + + self = rc_parse_value(buffer, memaddr, NULL, 0); + ASSERT_PTR_NOT_NULL(self); + + /* using remember allows for both sides to explicitly be cast to integer before + * performing the subtraction. */ + + /* float(4) = 1.5, prev(float(4)) = 0.0. 15-0 => 15=1 is false => 0 */ + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 0); + + /* float(4) = 1.75, prev(float(4)) = 1.5. 17-15 => 2=1 is false => 0 */ + ram[7] = 0x3f; ram[6] = 0xe0; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 0); + + /* float(4) = 1.82, prev(float(4)) = 1.75. 18-17 => 1=1 is true => 1 */ + ram[6] = 0xe8; ram[5] = 0xf5; ram[4] = 0xc3; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 1); + + /* float(4) = 2.06, prev(float(4)) = 1.82. 20-18 => 2=1 is false => 1 */ + ram[7] = 0x40; ram[6] = 0x03; ram[5] = 0xd7; ram[4] = 0x0a; + ASSERT_NUM_EQUALS(rc_evaluate_value(self, peek, &memory, NULL), 1); +} + void test_value(void) { TEST_SUITE_BEGIN(); @@ -785,5 +861,8 @@ void test_value(void) { test_typed_value_modulus(); test_typed_value_negation(); + test_addhits_float_coercion(); + test_addhits_float_coercion_remembered(); + TEST_SUITE_END(); }